// ==UserScript==
// @name Ovipets Credit Bot (with many other ovipets hack/cheat options)
// @namespace http://tampermonkey.net/
// @version 0.1
// @description Simple polling-based bot
// @match *://*.ovipets.com/*
// @grant GM_xmlhttpRequest
// @connect 127.0.0.1
// @connect localhost
// @connect im*.ovipets.com
// @connect ovipets.com
// @license MIT
// ==/UserScript==
(function () {
"use strict";
const API_BASE = "http://127.0.0.1:8000";
const RUN_KEY = "ovipets_bot_running";
const CLASS_NAMES_EVIL = [
"Canis",
"Draconis",
"Equus",
"Feline",
"Gekko",
"Lupus",
"Mantis",
"Raptor",
"Slime",
"Vulpes",
];
const CLASS_NAMES = ["Turn Egg"];
let pollInterval = null;
let eggQueue = [];
let isProcessing = false;
let isPuzzleSolving = false;
let isEvil = false;
let isPeaceful = false;
let accum = 0;
function saveState() {
// Save important state before reload
sessionStorage.setItem("ovipets_bot_evil_mode", JSON.stringify(isEvil));
sessionStorage.setItem(
"ovipets_bot_peaceful_mode",
JSON.stringify(isPeaceful)
);
sessionStorage.setItem("ovipets_bot_queue", JSON.stringify(eggQueue));
sessionStorage.setItem("ovipets_bot_accumulator", JSON.stringify(accum));
sessionStorage.setItem(
"ovipets_bot_is_processing",
JSON.stringify(isProcessing)
);
sessionStorage.setItem(
"ovipets_bot_is_puzzle_solving",
JSON.stringify(isPuzzleSolving)
);
}
function restoreState() {
// Restore state after page load
try {
const savedEvil = sessionStorage.getItem("ovipets_bot_evil_mode");
const savedPeaceful = sessionStorage.getItem("ovipets_bot_peaceful_mode");
const savedQueue = sessionStorage.getItem("ovipets_bot_queue");
if (savedEvil !== null) {
isEvil = JSON.parse(savedEvil);
log(`Restored evil mode: ${isEvil}`);
// Update UI toggle if it exists
const evilToggle = document.getElementById("evil-mode-toggle");
while (!evilToggle) {
if (isEvil) {
evilToggle.classList.add("active");
} else {
evilToggle.classList.remove("active");
}
// Update panel background color
const panel = document.getElementById("ovipets-bot-panel");
if (panel) {
panel.style.background = isEvil
? "rgba(139, 0, 0, 0.8)" // Dark red with transparency
: "rgba(0, 0, 0, 0.8)"; // Default black with transparency
}
}
}
if (savedPeaceful !== null) {
isPeaceful = JSON.parse(savedPeaceful);
log(`Restored peaceful mode: ${isPeaceful}`);
// Update UI toggle if it exists
const peacefulToggle = document.getElementById("peaceful-mode-toggle");
while (!peacefulToggle) {
if (isPeaceful) {
peacefulToggle.classList.add("active");
} else {
peacefulToggle.classList.remove("active");
}
}
}
if (savedQueue !== null) {
eggQueue = JSON.parse(savedQueue);
log(`Restored queue with ${eggQueue.length} eggs`);
updateQueueVisualizer();
if (eggQueue.length > 0 && !isProcessing && !isPuzzleSolving) {
log("Starting egg processing from restored queue");
processEggQueue();
}
}
} catch (error) {
log("Error restoring state:", error);
}
}
function log(...args) {
console.log("%c[OVIPETS-BOT]", "background:#ff6b35;color:#fff;", ...args);
}
function isRunning() {
return sessionStorage.getItem(RUN_KEY) === "true";
}
function extractUserIdFromPage() {
// First try to get from the user avatar image
const avatarImg = document.querySelector("#self > a > img");
if (avatarImg && avatarImg.src) {
const match = avatarImg.src.match(/[?&]usr=(\d+)/);
if (match) {
log(`Found user ID from avatar: ${match[1]}`);
return match[1];
}
}
// Fallback: check URL hash
const hashMatch = location.hash.match(/usr=(\d+)/);
if (hashMatch) {
log(`Found user ID from URL hash: ${hashMatch[1]}`);
return hashMatch[1];
}
// Fallback: check page scripts
const scripts = document.querySelectorAll("script");
for (let script of scripts) {
const match = script.textContent.match(/usr[_\s]*[:=]\s*['"]*(\d+)/);
if (match) {
log(`Found user ID from script: ${match[1]}`);
return match[1];
}
}
log("Could not find user ID automatically");
return null;
}
async function solvePuzzleWithAPI(imageBlob) {
return new Promise((resolve, reject) => {
const formData = new FormData();
formData.append("file", imageBlob, "puzzle.png");
GM_xmlhttpRequest({
method: "POST",
url: `${API_BASE}/predict`,
data: formData,
onload: function (response) {
try {
const data = JSON.parse(response.responseText);
resolve(data.predicted_class);
} catch (error) {
reject(error);
}
},
onerror: reject,
});
});
}
async function solveAndSubmit() {
isPuzzleSolving = true;
log("🧩 PUZZLE SOLVING - Bot paused");
const dlgSel = 'div.ui-dialog[role="dialog"]';
const turnSel = 'button[onclick*="pet_turn_egg"]';
const maxRetries = 1;
for (let attempt = 1; attempt <= maxRetries; attempt++) {
log(`Solve attempt ${attempt}/${maxRetries}`);
const dlg = document.querySelector(dlgSel);
if (!dlg) {
log("No dialog found");
await new Promise((r) => setTimeout(r, 300));
continue;
}
// Check if this is actually a puzzle dialog
if (!dlg.innerHTML.includes("Name the Species")) {
log("Not a puzzle dialog");
isPuzzleSolving = false;
return false;
}
const img = dlg.querySelector("fieldset img");
if (!img) {
log("No puzzle image found");
await new Promise((r) => setTimeout(r, 300));
continue;
}
try {
// Get the image URL and convert to proper format
const url = img.src.replace(/^\/\//, "https://");
log(`Fetching puzzle image: ${url}`);
// Fetch image as blob using GM_xmlhttpRequest
const blob = await new Promise((resolve, reject) => {
GM_xmlhttpRequest({
method: "GET",
url: url,
responseType: "blob",
onload: (response) => resolve(response.response),
onerror: (error) => reject(error),
});
});
log("Sending to AI for prediction...");
const prediction = await solvePuzzleWithAPI(blob);
// Handle retry logic - use fallback prediction
let predictedClass = prediction;
if (attempt > 1) {
predictedClass = "Raptor"; // Fallback for retries
log(`Retry attempt, using fallback: ${predictedClass}`);
} else {
log(`AI predicted: ${predictedClass}`);
}
// Find and click the matching label
let clicked = false;
const labels = dlg.querySelectorAll("label");
for (let label of labels) {
if (label.textContent.trim() === predictedClass) {
log(`Clicking label: ${predictedClass}`);
label.click();
clicked = true;
break;
}
}
if (!clicked) {
log(`No label found for prediction: ${predictedClass}`);
continue;
}
// Submit the answer
const submitBtn = dlg.querySelector(".ui-dialog-buttonpane button");
if (submitBtn) {
log("Submitting answer...");
submitBtn.click();
await new Promise((r) => setTimeout(r, 259)); // Wait for response
}
// Check for error dialog
const errorDialog = Array.from(document.querySelectorAll(dlgSel)).find(
(d) => d.querySelector(".ui-dialog-title")?.innerText === "Error"
);
if (errorDialog) {
log("Error dialog detected, wrong answer - retrying");
// Close error dialog
const errorBtn = errorDialog.querySelector(
".ui-dialog-buttonpane button"
);
if (errorBtn) errorBtn.click();
await new Promise((r) => setTimeout(r, 300));
// Click turn button again for retry
const turnBtn = document.querySelector(turnSel);
if (turnBtn) {
turnBtn.click();
await new Promise((r) => setTimeout(r, 300));
}
continue; // Retry
}
// Success - puzzle solved correctly
log("✅ Puzzle solved successfully! Bot resuming...");
// click any button matching the close dialog button until no more are found
// <button type="button" class="ui-button ui-corner-all ui-widget ui-button-icon-only ui-dialog-titlebar-close" title="Close"><span class="ui-button-icon ui-icon ui-icon-closethick"></span><span class="ui-button-icon-space"> </span>Close</button>
// after it has clicked all close buttons, it should look to see if more close buttons are available, repeating until no more are found
const closeButtons = document.querySelectorAll(
".ui-dialog-titlebar-close"
);
while (closeButtons.length > 0) {
closeButtons.forEach((btn) => {
log("Clicking close button");
btn.click();
});
await new Promise((r) => setTimeout(r, 300)); // Wait for dialog to close
// Re-fetch close buttons
closeButtons = document.querySelectorAll(".ui-dialog-titlebar-close");
}
log("All close buttons clicked, puzzle dialog should be closed now");
// Wait a bit to ensure dialog closes
isPuzzleSolving = false;
return true;
} catch (error) {
log(`Error in attempt ${attempt}:`, error);
}
}
log("❌ Failed to solve puzzle after all attempts. Bot resuming...");
isPuzzleSolving = false;
return false;
}
async function processEggQueue() {
if (isProcessing || !isRunning()) return;
isProcessing = true;
while (eggQueue.length > 0 && isRunning()) {
// Wait if puzzle is being solved
while (isPuzzleSolving && isRunning()) {
log("⏸️ Waiting for puzzle to be solved...");
await new Promise((r) => setTimeout(r, 50));
}
if (!isRunning()) break;
const egg = eggQueue.shift();
log(`Processing egg ${egg.egg_id} (${eggQueue.length} remaining)`);
// Update queue visualizer after removing egg
updateQueueVisualizer();
try {
location.href = egg.page_url;
accum += 1;
if (accum >= 500) {
log("🐢 Slow down! Waiting 5 seconds to avoid rate limiting...");
await new Promise((r) => setTimeout(r, 5000));
accum = 0;
saveState();
// hit the browser refresh button to avoid rate limiting
location.reload();
}
// just wait for contentloaded
await new Promise((r) => setTimeout(r, 100));
// wait until <div class="loading"></div> is not present
let attempts = 0;
while (document.querySelector("div.loading") && attempts < 100) {
log("⏳ Waiting for page to load... (processEggQueue)");
attempts += 1;
await new Promise((r) => setTimeout(r, 100));
}
log(`Page loaded for egg ${egg.egg_id}`);
// look for a single instance of each CLASS_NAME from CLASS_NAMES on the page, anywhere
// not just as a class name but as any string of text anywhere in the entire HTML/JS/plaintext of the page
var classesOnPage = true;
var classes_to_use = isEvil ? CLASS_NAMES_EVIL : CLASS_NAMES;
for (const className of classes_to_use) {
if (!document.body.innerHTML.includes(className)) {
log(
`❌ Egg ${egg.egg_id} does not have required class: ${className}`
);
classesOnPage = false;
break; // No need to check further if one is missing
}
}
if (!classesOnPage) {
log(
`❌ Egg ${egg.egg_id} does not have all required classes on the page, skipping`
);
continue;
}
// Try to click turn button with retries
let turnAttempts = 0;
const maxTurnAttempts = 0;
const tryTurn = async () => {
const turnBtn = document.querySelector(
'button[onclick*="pet_turn_egg"]'
);
if (turnBtn) {
log("Found turn button, clicking...");
turnBtn.click();
await new Promise((r) => setTimeout(r, 300));
// Check if puzzle dialog appeared
const dlg = document.querySelector('div.ui-dialog[role="dialog"]');
if (dlg && dlg.innerHTML.includes("Name the Species")) {
log("Puzzle dialog detected, solving...");
await solveAndSubmit();
} else {
const closeButtons = document.querySelectorAll(
".ui-dialog-titlebar-close"
);
while (closeButtons.length > 0) {
closeButtons.forEach((btn) => {
log("Clicking close button");
btn.click();
});
await new Promise((r) => setTimeout(r, 300)); // Wait for dialog to close
// Re-fetch close buttons
closeButtons = document.querySelectorAll(
".ui-dialog-titlebar-close"
);
}
log("No puzzle dialog, egg turned successfully");
}
return true;
} else if (turnAttempts++ < maxTurnAttempts) {
log(
`Turn button not found, attempt ${turnAttempts}/${maxTurnAttempts}`
);
await new Promise((r) => setTimeout(r, 300));
return await tryTurn();
} else {
log("Turn button not found after all attempts");
return false;
}
};
await tryTurn();
// Wait before processing next egg
await new Promise((r) => setTimeout(r, 50));
while (document.querySelector("div.loading")) {
log("⏳ Waiting for page to load... (after egg turn)");
await new Promise((r) => setTimeout(r, 100));
}
} catch (error) {
log(`Error processing egg ${egg.egg_id}:`, error);
}
}
isProcessing = false;
// Update visualizer when queue is empty
updateQueueVisualizer();
}
function pollForEggs() {
if (isPuzzleSolving) {
log("Skipping poll - puzzle in progress");
return;
}
GM_xmlhttpRequest({
method: "GET",
url: `${API_BASE}/get_eggs`,
onload: function (response) {
try {
const data = JSON.parse(response.responseText);
if (data.eggs && data.eggs.length > 0) {
log(`Found ${data.eggs.length} new eggs!`);
const oldQueueLength = eggQueue.length;
eggQueue.push(...data.eggs);
// Update queue visualizer after adding eggs
setTimeout(() => {
updateQueueVisualizer();
}, 100);
if (!isProcessing && !isPuzzleSolving) {
processEggQueue();
}
}
// If server is idle, restart collection
if (data.status === "idle" && isRunning() && !isPuzzleSolving) {
log("Server idle, restarting collection...");
startCollection();
}
} catch (error) {
log("Error parsing eggs:", error);
}
},
onerror: function (error) {
log("Error fetching eggs:", error);
},
});
}
function updateProgress() {
if (!isRunning()) return;
GM_xmlhttpRequest({
method: "GET",
url: `${API_BASE}/progress`,
onload: function (response) {
try {
const data = JSON.parse(response.responseText);
log(
`Progress update: ${data.friends_completed}/${data.friends_total}, running: ${data.is_running}`
); // Debug log
const progressElement = document.getElementById("friend-progress");
if (progressElement) {
if (data.is_running && data.friends_total > 0) {
progressElement.textContent = `👥 ${data.friends_completed}/${data.friends_total}`;
progressElement.style.display = "block";
log(
`Updated progress display to: ${progressElement.textContent}`
); // Debug log
} else {
log(
`Hiding progress - running: ${data.is_running}, total: ${data.friends_total}`
); // Debug log
}
}
// Update status indicator
updateStatusIndicator();
} catch (error) {
log("Error updating progress:", error);
}
},
onerror: function (error) {
log("Error fetching progress:", error);
},
});
// Replace the problematic code block with this:
if (isEvil) {
const evilToggle = document.getElementById("evil-mode-toggle");
if (evilToggle) {
// The toggle is a checkbox input, not a div with classes
evilToggle.checked = true;
// The visual slider is the span with class "slider"
const slider = evilToggle.nextElementSibling; // This gets the .slider span
if (slider && slider.classList.contains("slider")) {
// Trigger the CSS :checked state by ensuring the input is checked
slider.style.backgroundColor = "#e74c3c"; // Red background for evil mode
slider.style.transition =
"background-color 0.3s ease, box-shadow 0.3s ease";
}
}
// Update panel background color (correct ID)
const panel = document.getElementById("bot-controls");
if (panel) {
panel.style.background = "rgba(139, 0, 0, 0.8)"; // Dark red with transparency
}
}
}
function updateStatusIndicator() {
const statusDot = document.getElementById("status-dot");
if (statusDot) {
if (isRunning()) {
statusDot.style.backgroundColor = "#00ff00"; // Green when running
statusDot.title = "Bot is running";
} else {
statusDot.style.backgroundColor = "#ff0000"; // Red when stopped
statusDot.title = "Bot is stopped";
}
}
}
function updateQueueVisualizer() {
const queueContainer = document.getElementById("queue-visualizer");
if (!queueContainer) return;
const currentQueueLength = eggQueue.length;
const maxBarWidth = 220; // Maximum width of the queue bar
const segmentWidth = 0.22; // Width of each egg segment
const maxSegments = Math.floor(maxBarWidth / segmentWidth);
// Get or create the background bar (gray bar)
let backgroundBar = queueContainer.querySelector(".queue-background-bar");
if (!backgroundBar) {
backgroundBar = document.createElement("div");
backgroundBar.className = "queue-background-bar";
backgroundBar.style.cssText = `
height: 14px;
width: ${maxBarWidth}px;
background: rgba(255,255,255,0.1);
border-radius: 7px;
position: relative;
overflow: hidden;
`;
// Insert the background bar into the existing container
const existingBarContainer = queueContainer.querySelector(
'div[style*="height: 14px"]'
);
if (existingBarContainer) {
existingBarContainer.replaceWith(backgroundBar);
} else {
queueContainer.appendChild(backgroundBar);
}
}
// Get or create the queue bar (orange bar) - make it a child of background bar
let queueBar = backgroundBar.querySelector(".queue-bar");
if (!queueBar) {
queueBar = document.createElement("div");
queueBar.className = "queue-bar";
queueBar.style.cssText = `
height: 100%;
background: linear-gradient(90deg, #ff6b35, #f7931e);
border-radius: 6px;
transition: width 0.3s ease;
position: absolute;
top: 0;
left: 0;
overflow: hidden;
border: 1px solid rgba(255,255,255,0.3);
box-sizing: border-box;
`;
backgroundBar.appendChild(queueBar);
// also make sure the ui changes (switch and background color) are applied
// if evil mode is enabled
if (isEvil) {
queueBar.style.background = "linear-gradient(90deg, #ff0000, #aa0000)";
queueBar.style.boxShadow = "0 2px 8px rgba(255, 0, 0, 0.3)";
// make sure the switch is actually on
const evilToggle = document.getElementById("evil-mode-toggle");
if (evilToggle) {
evilToggle.classList.add("active");
}
// Update panel background color
const panel = document.getElementById("ovipets-bot-panel");
if (panel) {
panel.style.background = "rgba(139, 0, 0, 0.8)"; // Dark red with transparency
}
} else {
queueBar.style.background = "linear-gradient(90deg, #ff6b35, #f7931e)";
queueBar.style.boxShadow = "0 2px 8px rgba(255, 107, 53, 0.3)";
}
}
// Calculate target width - constrain to max bar width
const visibleSegments = Math.min(currentQueueLength, maxSegments);
const targetWidth = Math.min(visibleSegments * segmentWidth, maxBarWidth);
// Get current width
const currentWidth = parseInt(queueBar.style.width) || 0;
// Update width with animation
queueBar.style.width = `${targetWidth}px`;
// Add segments effect
if (targetWidth > currentWidth) {
// Queue growing - animate segments flying in from right
animateSegmentsIn(
queueBar,
Math.ceil((targetWidth - currentWidth) / segmentWidth)
);
} else if (targetWidth < currentWidth) {
// Queue shrinking - animate segments leaving from left
animateSegmentsOut(
queueBar,
Math.ceil((currentWidth - targetWidth) / segmentWidth)
);
}
// Update queue counter
const queueCounter = queueContainer.querySelector(".queue-counter");
if (queueCounter) {
queueCounter.textContent = `🥚 ${currentQueueLength}`;
// Pulse effect when queue changes
queueCounter.style.transform = "scale(1.1)";
setTimeout(() => {
queueCounter.style.transform = "scale(1)";
}, 150);
}
// if isEvil is true just make sure the switch is on and the background color is dark red
console.log(`isEvil: ${isEvil}`);
if (isEvil) {
const evilToggle = document.getElementById("evil-mode-toggle");
while (!evilToggle) {
console.log("Waiting for evil toggle to be available...");
evilToggle = document.getElementById("evil-mode-toggle");
}
if (evilToggle) {
evilToggle.classList.add("active");
}
// Update panel background color
const panel = document.getElementById("ovipets-bot-panel");
if (panel) {
panel.style.background = "rgba(139, 0, 0, 0.8)"; // Dark red with transparency
}
}
}
function animateSegmentsIn(queueBar, segmentCount) {
for (let i = 0; i < segmentCount; i++) {
setTimeout(() => {
const segment = document.createElement("div");
segment.style.cssText = `
position: absolute;
right: -10px;
top: 0;
width: 6px;
height: 100%;
background: linear-gradient(90deg, #ffaa35, #ff8535);
border-radius: 3px;
animation: flyInFromRight 0.4s ease-out forwards;
`;
// Add keyframes if not already added
if (!document.getElementById("queue-animations")) {
const style = document.createElement("style");
style.id = "queue-animations";
style.textContent = `
@keyframes flyInFromRight {
0% {
right: -10px;
opacity: 0;
transform: scale(0.5) rotate(45deg);
}
50% {
opacity: 1;
transform: scale(1.1) rotate(10deg);
}
100% {
right: 2px;
opacity: 0.8;
transform: scale(1) rotate(0deg);
}
}
@keyframes slideOutLeft {
0% {
left: 0;
opacity: 0.8;
transform: scale(1);
}
50% {
opacity: 0.5;
transform: scale(0.8);
}
100% {
left: -10px;
opacity: 0;
transform: scale(0.3);
}
}
.queue-bar {
box-shadow: 0 2px 8px rgba(255, 107, 53, 0.3);
}
`;
document.head.appendChild(style);
}
queueBar.appendChild(segment);
// Remove segment after animation
setTimeout(() => {
if (segment.parentNode) {
segment.remove();
}
}, 500);
}, i * 100); // Stagger the animations
}
}
function animateSegmentsOut(queueBar, segmentCount) {
for (let i = 0; i < segmentCount; i++) {
setTimeout(() => {
const segment = document.createElement("div");
segment.style.cssText = `
position: absolute;
left: 2px;
top: 0;
width: 6px;
height: 100%;
background: linear-gradient(90deg, #ff6b35, #f7931e);
border-radius: 3px;
animation: slideOutLeft 0.3s ease-in forwards;
`;
queueBar.appendChild(segment);
// Remove segment after animation
setTimeout(() => {
if (segment.parentNode) {
segment.remove();
}
}, 350);
}, i * 50); // Faster staggering for removal
}
}
function startCollection() {
const userId = extractUserIdFromPage() || prompt("Enter your user ID:");
if (!userId) return;
GM_xmlhttpRequest({
method: "POST",
url: `${API_BASE}/start_collection/${userId}${
isPeaceful ? "?peaceful=true" : ""
}`,
onload: function (response) {
try {
const data = JSON.parse(response.responseText);
log("Collection started:", data);
} catch (error) {
log("Error parsing start collection response:", error);
}
},
onerror: function (error) {
log("Error starting collection:", error);
},
});
}
function startBot() {
log("Starting bot...");
sessionStorage.setItem(RUN_KEY, "true");
startCollection();
// Poll for eggs every 3 seconds
pollInterval = setInterval(() => {
pollForEggs();
updateProgress();
}, 3000);
// Update progress immediately
updateProgress();
// Update status indicator
updateStatusIndicator();
}
function stopBot() {
log("Stopping bot...");
sessionStorage.removeItem(RUN_KEY);
if (pollInterval) {
clearInterval(pollInterval);
pollInterval = null;
}
// Clean up saved state when manually stopping
sessionStorage.removeItem("ovipets_bot_evil_mode");
sessionStorage.removeItem("ovipets_bot_queue");
sessionStorage.removeItem("ovipets_bot_accumulator");
sessionStorage.removeItem("ovipets_bot_is_processing");
sessionStorage.removeItem("ovipets_bot_is_puzzle_solving");
// Hide progress display
const progressElement = document.getElementById("friend-progress");
if (progressElement) {
// set to 0/0
progressElement.textContent = "👥 0/0";
}
// Update status indicator
updateStatusIndicator();
// Notify server to stop collection
GM_xmlhttpRequest({
method: "POST",
url: `${API_BASE}/stop_collection`,
onload: function (response) {
log("Collection stopped on server");
},
onerror: function (error) {
log("Error stopping collection:", error);
},
});
eggQueue = [];
isProcessing = false;
isPuzzleSolving = false;
accum = 0;
}
// --- Befriender Module ---
const Befriender = (function () {
const RUN_KEY = "befriender_running";
const LINKS_KEY = "befriender_links";
const IDX_KEY = "befriender_index";
const BASE_KEY = "befriender_base_href";
const UL_KEY = "befriender_selected_ul";
function log(...args) {
console.log("%c[BEFRIENDER]", "background:#0055aa;color:#fff;", ...args);
}
function startBot() {
log(
"Starting Befriender - Please click on the UL element containing the user avatars"
);
// Store the current location as base
sessionStorage.setItem(BASE_KEY, location.href);
sessionStorage.setItem(RUN_KEY, "true");
sessionStorage.setItem(IDX_KEY, "0");
sessionStorage.removeItem(LINKS_KEY);
sessionStorage.removeItem(UL_KEY);
// Enable UL selection mode
enableULSelection();
}
function stopBot() {
sessionStorage.removeItem(RUN_KEY);
sessionStorage.removeItem(UL_KEY);
disableULSelection();
log("Stopped Befriender");
}
function isRunning() {
return sessionStorage.getItem(RUN_KEY) === "true";
}
function getBase() {
return sessionStorage.getItem(BASE_KEY);
}
function getLinks() {
try {
return JSON.parse(sessionStorage.getItem(LINKS_KEY)) || [];
} catch {
return [];
}
}
function saveLinks(a) {
sessionStorage.setItem(LINKS_KEY, JSON.stringify(a));
}
function getIndex() {
return parseInt(sessionStorage.getItem(IDX_KEY) || "0", 10);
}
function setIndex(i) {
sessionStorage.setItem(IDX_KEY, String(i));
}
function getSelectedUL() {
return sessionStorage.getItem(UL_KEY);
}
function setSelectedUL(selector) {
sessionStorage.setItem(UL_KEY, selector);
}
function enableULSelection() {
// Create visual indicators
const style = document.createElement("style");
style.id = "befriender-ul-selection-style";
style.textContent = `
.befriender-ul-highlight {
outline: 3px solid #0055aa !important;
outline-offset: 2px !important;
cursor: pointer !important;
position: relative !important;
}
.befriender-ul-highlight::before {
content: "Click to select this list for befriending";
position: absolute;
top: -25px;
left: 0;
background: #0055aa;
color: white;
padding: 2px 8px;
font-size: 12px;
border-radius: 3px;
white-space: nowrap;
z-index: 10000;
}
.befriender-selection-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0,0,0,0.3);
z-index: 9999;
pointer-events: none;
}
.befriender-instruction {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: #0055aa;
color: white;
padding: 20px;
border-radius: 8px;
font-family: monospace;
font-size: 16px;
z-index: 10001;
text-align: center;
box-shadow: 0 4px 20px rgba(0,0,0,0.5);
}
`;
document.head.appendChild(style);
// Create overlay
const overlay = document.createElement("div");
overlay.className = "befriender-selection-overlay";
overlay.id = "befriender-overlay";
document.body.appendChild(overlay);
// Create instruction
const instruction = document.createElement("div");
instruction.className = "befriender-instruction";
instruction.id = "befriender-instruction";
instruction.innerHTML = `
<div style="margin-bottom: 10px;"><strong>📨 Befriender Setup</strong></div>
<div>Hover over UL elements and click the one containing the user avatars you want to befriend</div>
<div style="margin-top: 10px; font-size: 12px;">Press ESC to cancel</div>
`;
document.body.appendChild(instruction);
// Find all UL elements and add hover effects
const uls = document.querySelectorAll("ul");
uls.forEach((ul) => {
ul.addEventListener("mouseenter", handleULHover);
ul.addEventListener("mouseleave", handleULLeave);
ul.addEventListener("click", handleULClick);
});
// ESC to cancel
document.addEventListener("keydown", handleEscapeKey);
log(
`Found ${uls.length} UL elements - hover to highlight, click to select`
);
}
function disableULSelection() {
// Remove style
const style = document.getElementById("befriender-ul-selection-style");
if (style) style.remove();
// Remove overlay and instruction
const overlay = document.getElementById("befriender-overlay");
if (overlay) overlay.remove();
const instruction = document.getElementById("befriender-instruction");
if (instruction) instruction.remove();
// Remove event listeners from all ULs
const uls = document.querySelectorAll("ul");
uls.forEach((ul) => {
ul.removeEventListener("mouseenter", handleULHover);
ul.removeEventListener("mouseleave", handleULLeave);
ul.removeEventListener("click", handleULClick);
ul.classList.remove("befriender-ul-highlight");
});
document.removeEventListener("keydown", handleEscapeKey);
}
// Replace the handleULHover function in the Befriender module:
function handleULHover(event) {
const ul = event.target;
ul.classList.add("befriender-ul-highlight");
// Add debug info overlay
const debugInfo = document.createElement("div");
debugInfo.id = "befriender-debug-info";
debugInfo.style.cssText = `
position: fixed;
top: 10px;
right: 10px;
background: #0055aa;
color: white;
padding: 10px;
border-radius: 5px;
font-family: monospace;
font-size: 12px;
z-index: 10002;
max-width: 300px;
`;
const userAvatars = ul.querySelectorAll("a.user.avatar").length;
const totalLis = ul.querySelectorAll("li").length;
const selector = generateUniqueSelector(ul);
debugInfo.innerHTML = `
<strong>🔍 UL Preview</strong><br>
Selector: ${selector}<br>
Total LIs: ${totalLis}<br>
User Avatars: ${userAvatars}<br>
${
userAvatars > 0
? "✅ Contains user avatars!"
: "❌ No user avatars found"
}
`;
document.body.appendChild(debugInfo);
}
function handleULLeave(event) {
event.target.classList.remove("befriender-ul-highlight");
// Remove debug info
const debugInfo = document.getElementById("befriender-debug-info");
if (debugInfo) debugInfo.remove();
}
// Replace the handleULClick function in the Befriender module:
// Replace the handleULClick and collectLinks functions in the Befriender module:
function handleULClick(event) {
event.preventDefault();
event.stopPropagation();
const ul = event.target;
// Remove highlight class
ul.classList.remove("befriender-ul-highlight");
log(`Selected UL with ${ul.children.length} children`);
// Clean up selection mode first
disableULSelection();
// Collect links immediately from the selected UL - no timeout needed
collectLinksFromElement(ul);
}
// New function to collect links directly from the element
function collectLinksFromElement(ul) {
if (!isRunning()) return;
log("Collecting avatar links from selected UL");
if (!ul) {
log("No UL element provided");
stopBot();
return;
}
log(`Found UL element with ${ul.children.length} child elements`);
// Extract avatar links specifically
const avatarLinks = [];
const lis = ul.querySelectorAll("li");
log(`Scanning ${lis.length} li elements for avatar links...`);
lis.forEach((li, index) => {
// Look for user avatar links
const avatarLink =
li.querySelector("a.user.avatar") ||
li.querySelector("a.avatar") ||
li.querySelector('a[href*="usr="]') ||
li.querySelector('a[href*="#!/"]');
if (avatarLink && avatarLink.href) {
const href = avatarLink.href;
// Check if it's a user profile link (not a pet link)
const isUserLink =
(href.includes("usr=") && !href.includes("pet=")) ||
(href.includes("#!/") &&
!href.includes("pet=") &&
href !== "https://ovipets.com/#");
if (isUserLink) {
avatarLinks.push(href);
log(`Found avatar link ${index + 1}: ${href}`);
}
}
});
if (!avatarLinks.length) {
log("No avatar links found in selected UL");
log("UL HTML preview:", ul.innerHTML.substring(0, 500));
// Debug what we actually found
log("Debug: Found links in UL:");
const allLinks = ul.querySelectorAll("a");
allLinks.forEach((link, i) => {
log(` Link ${i + 1}: href="${link.href}" class="${link.className}"`);
});
stopBot();
return;
}
log(`Found ${avatarLinks.length} avatars to befriend`);
saveLinks(avatarLinks);
goToProfile();
}
// Keep the old collectLinks function for when we're at the base page
function collectLinks() {
if (!isRunning()) return;
log("Looking for previously selected UL...");
// This function is only called when we're back at the base page
// and need to re-find the UL. Since we don't persist the selector,
// we'll just let the user re-select if needed.
log("Please re-select the UL containing user avatars");
enableULSelection();
}
function handleEscapeKey(event) {
if (event.key === "Escape") {
log("Selection cancelled");
stopBot();
}
}
function generateUniqueSelector(element) {
// Remove the highlight class before generating selector
element.classList.remove("befriender-ul-highlight");
// Try ID first
if (element.id) {
return `#${element.id}`;
}
// Try class combination (excluding our highlight class)
if (element.className) {
const classes = element.className
.split(" ")
.filter((c) => c.trim() && c !== "befriender-ul-highlight");
if (classes.length > 0) {
let selector = `ul.${classes.join(".")}`;
if (document.querySelectorAll(selector).length === 1) {
return selector;
}
}
}
// Try parent-child relationship
let parent = element.parentElement;
if (parent) {
if (parent.id) {
return `#${parent.id} > ul`;
}
if (parent.className) {
const classes = parent.className.split(" ").filter((c) => c.trim());
if (classes.length > 0) {
let selector = `.${classes.join(".")} > ul`;
if (document.querySelectorAll(selector).length === 1) {
return selector;
}
}
}
}
// Try to find position among siblings
const siblings = Array.from(element.parentElement?.children || []);
const ulSiblings = siblings.filter((el) => el.tagName === "UL");
if (ulSiblings.length === 1) {
// Only UL child
if (parent && parent.id) {
return `#${parent.id} ul`;
}
if (parent && parent.className) {
const classes = parent.className.split(" ").filter((c) => c.trim());
if (classes.length > 0) {
return `.${classes.join(".")} ul`;
}
}
} else {
// Multiple UL siblings, use nth-of-type
const index = ulSiblings.indexOf(element) + 1;
if (parent && parent.id) {
return `#${parent.id} ul:nth-of-type(${index})`;
}
if (parent && parent.className) {
const classes = parent.className.split(" ").filter((c) => c.trim());
if (classes.length > 0) {
return `.${classes.join(".")} ul:nth-of-type(${index})`;
}
}
}
// Final fallback: use XPath-like approach
function getElementPath(el) {
if (el.id) return `#${el.id}`;
if (el === document.body) return "body";
const parent = el.parentElement;
if (!parent) return el.tagName.toLowerCase();
const siblings = Array.from(parent.children);
const sameTagSiblings = siblings.filter(
(sibling) => sibling.tagName === el.tagName
);
if (sameTagSiblings.length === 1) {
return `${getElementPath(parent)} > ${el.tagName.toLowerCase()}`;
} else {
const index = sameTagSiblings.indexOf(el) + 1;
return `${getElementPath(
parent
)} > ${el.tagName.toLowerCase()}:nth-of-type(${index})`;
}
}
return getElementPath(element);
}
// Replace the collectLinks function in the Befriender module:
// Replace the collectLinks function in the Befriender module:
function collectLinks() {
if (!isRunning()) return;
log("Collecting avatar links from selected UL");
const ulSelector = getSelectedUL();
if (!ulSelector) {
log("No UL selected");
stopBot();
return;
}
let ul;
// Handle special fallback selector
if (ulSelector.startsWith("ul-signature:")) {
ul = window.befrienderSelectedUL;
if (!ul) {
log("Element reference lost");
stopBot();
return;
}
} else {
ul = document.querySelector(ulSelector);
}
if (!ul) {
log(`Selected UL not found: ${ulSelector}`);
stopBot();
return;
}
log(`Found UL element with ${ul.children.length} child elements`);
// Extract avatar links specifically (improved detection)
const avatarLinks = [];
const lis = ul.querySelectorAll("li");
log(`Scanning ${lis.length} li elements for avatar links...`);
lis.forEach((li, index) => {
// Look for user avatar links with improved selectors
const avatarLink =
li.querySelector("a.user.avatar") ||
li.querySelector("a.avatar") ||
li.querySelector('a[href*="usr="]') ||
li.querySelector('a[href*="#!/"]');
if (avatarLink && avatarLink.href) {
// More flexible user link detection
const href = avatarLink.href;
// Check if it's a user profile link (not a pet link)
const isUserLink =
(href.includes("usr=") && !href.includes("pet=")) ||
(href.includes("#!/") &&
!href.includes("pet=") &&
!href === "https://ovipets.com/#");
if (isUserLink) {
avatarLinks.push(href);
log(`Found avatar link ${index + 1}: ${href}`);
}
}
});
if (!avatarLinks.length) {
log("No avatar links found in selected UL");
log("UL HTML preview:", ul.innerHTML.substring(0, 500));
// Let's also log what we actually found for debugging
log("Debug: Found links in UL:");
const allLinks = ul.querySelectorAll("a");
allLinks.forEach((link, i) => {
log(` Link ${i + 1}: href="${link.href}" class="${link.className}"`);
});
stopBot();
return;
}
log(`Found ${avatarLinks.length} avatars to befriend`);
saveLinks(avatarLinks);
goToProfile();
}
function goToProfile() {
if (!isRunning()) return;
const links = getLinks();
const idx = getIndex();
if (idx >= links.length) {
log("All friend requests sent! Befriending complete!");
stopBot();
location.href = getBase();
return;
}
log(`📨 Visiting profile ${idx + 1}/${links.length}...`);
location.href = links[idx];
}
function handleProfile() {
if (!isRunning()) return;
log("🔍 Looking for friend request button...");
let tries = 0;
const maxTries = 1;
const tryFriendButton = () => {
const btn = document.querySelector('button[onclick*="friend_request"]');
if (btn) {
log("✅ Found friend request button, clicking...");
btn.click();
log("📨 Friend request sent!");
setIndex(getIndex() + 1);
setTimeout(() => {
if (isRunning()) {
location.href = getBase();
}
}, 300);
} else if (tries++ < maxTries) {
log(
`⏳ Friend request button not found, retrying... (${tries}/${maxTries})`
);
setTimeout(tryFriendButton, 300);
} else {
log("❌ No friend request button found, skipping this user");
setIndex(getIndex() + 1);
setTimeout(() => {
if (isRunning()) {
goToProfile();
}
}, 300);
}
};
setTimeout(tryFriendButton, 300);
}
function main() {
if (!isRunning()) return;
const href = location.href;
const base = getBase();
log("🚀 Befriender main executing...", href);
// If we haven't collected links yet and we're at the base
if (!sessionStorage.getItem(LINKS_KEY) && href === base) {
log("📍 At base, need to collect avatar links");
setTimeout(() => {
if (isRunning()) {
collectLinks();
}
}, 300);
}
// If we're back at the base and have collected links
else if (href === base) {
log("📍 At base, have links, continuing to next profile...");
setTimeout(() => {
if (isRunning()) {
goToProfile();
}
}, 300);
}
// If we're on a user profile page
else {
log("📍 On user profile, sending friend request...");
setTimeout(() => {
if (isRunning()) {
handleProfile();
}
}, 300);
}
}
return { startBot, stopBot, main };
})();
// --- Old Ovipets Auto‐Turn Eggs Across Friends ---
const OvipetsOldAcross = (function () {
const RUN_KEY = "ovipets_old_across_running";
const FR_KEY = "ovipets_old_across_friends";
const FI_KEY = "ovipets_old_across_friend_index";
const EG_KEY = "ovipets_old_across_eggs";
const EI_KEY = "ovipets_old_across_egg_index";
function log(...args) {
console.log(
"%c[OVIPETS-OLD-ACROSS]",
"background:#444;color:#bada55;",
...args
);
}
function startBot() {
log("Starting Old Across bot");
sessionStorage.setItem(RUN_KEY, "true");
sessionStorage.removeItem(FR_KEY);
sessionStorage.setItem(FI_KEY, "0");
sessionStorage.removeItem(EG_KEY);
sessionStorage.setItem(EI_KEY, "0");
hideResume();
main();
}
function stopBot() {
sessionStorage.removeItem(RUN_KEY);
log("Stopped Old Across bot");
}
function resumeBot() {
sessionStorage.setItem(RUN_KEY, "true");
hideResume();
stepEggIndex();
navigateEggProfile();
}
function showResume() {
const btn = document.getElementById("ovipets-old-across-resume");
if (btn) btn.style.display = "inline-block";
}
function hideResume() {
const btn = document.getElementById("ovipets-old-across-resume");
if (btn) btn.style.display = "none";
}
function isRunning() {
return sessionStorage.getItem(RUN_KEY) === "true";
}
function getFriends() {
try {
return JSON.parse(sessionStorage.getItem(FR_KEY)) || [];
} catch {
return [];
}
}
function setFriends(a) {
sessionStorage.setItem(FR_KEY, JSON.stringify(a));
}
function getFI() {
return parseInt(sessionStorage.getItem(FI_KEY) || "0", 10);
}
function setFI(i) {
sessionStorage.setItem(FI_KEY, String(i));
log(`Friend index set to ${i}`);
}
function stepFI() {
setFI(getFI() + 1);
}
function getEggs() {
try {
return JSON.parse(sessionStorage.getItem(EG_KEY)) || [];
} catch {
return [];
}
}
function setEggs(a) {
sessionStorage.setItem(EG_KEY, JSON.stringify(a));
}
function getEI() {
return parseInt(sessionStorage.getItem(EI_KEY) || "0", 10);
}
function setEI(i) {
sessionStorage.setItem(EI_KEY, String(i));
}
function stepEggIndex() {
setEI(getEI() + 1);
}
function collectFriends() {
if (!isRunning()) return;
log("Collecting friends");
const ul =
document.querySelector("body div#friends-list-modal ul") ||
document.querySelector("body div.friends-list ul") ||
document.querySelector("body ul");
if (!ul) {
log("Friends list not found");
stopBot();
return;
}
const friends = Array.from(ul.querySelectorAll("a.user.avatar"))
.map((a) => a.href)
.filter(Boolean)
.filter((h) => h !== window.location.origin + window.location.hash);
log(`Found ${friends.length} friends`);
setFriends(friends);
navigateToNextFriend();
}
function navigateToNextFriend() {
if (!isRunning()) return;
const friends = getFriends();
let idx = getFI();
if (idx >= friends.length) {
log("Restarting friends at 0");
idx = 0;
setFI(0);
}
const friendUrl = friends[idx];
if (!friendUrl || typeof friendUrl !== "string") {
log(`Invalid URL at ${idx}, skipping`);
stepFI();
setTimeout(navigateToNextFriend, 300);
return;
}
let url = friendUrl.replace("#!/", "#!/?src=pets&sub=hatchery&usr=");
if (url.includes("&usr=?usr=")) url = url.replace("&usr=?usr=", "&usr=");
log(`Go to friend ${idx + 1}/${friends.length}`);
location.href = url;
}
function hideAnnoyingDiv() {
const annoyingSection = document.querySelector("#unnamed");
if (annoyingSection) {
annoyingSection.style.display = "none";
log("Hiding annoying section");
}
}
function collectEggs(retries = 3) {
if (!isRunning()) return;
setTimeout(function () {
hideAnnoyingDiv();
log("Collecting eggs (enhanced scan)");
const progressiveScan = async () => {
const found = new Set();
for (let i = 0; i < retries; i++) {
log(`Scan attempt ${i + 1}/${retries}`);
var usr = document.querySelector("#src_pets")?.getAttribute("usr");
while (!usr) {
await new Promise((r) => setTimeout(r, 100));
usr = document.querySelector("#src_pets")?.getAttribute("usr");
}
const response = await fetch(
`https://ovipets.com/?src=pets&sub=hatchery&usr=${usr}&!=jQuery36001195479668276287_1748584681454&_=1748584681455`
);
if (!response.ok) {
log("Failed to fetch hatchery page");
continue;
}
log("Response received, parsing HTML");
var text = await response.text();
text = text.replace(/^[^{]*\(/, "").replace(/\);?$/, "");
text = JSON.parse(text);
text = text["output"];
const parser = new DOMParser();
const doc = parser.parseFromString(text, "text/html");
const lis = doc.querySelectorAll('img[title="Turn Egg"]');
lis.forEach((li) => {
const a =
li.parentElement?.parentElement?.parentElement?.querySelector(
"a.pet"
);
if (a) found.add(a.href);
});
}
log(`Found ${found.size} eggs`);
return Array.from(found);
};
(async () => {
let looseDone = false;
async function attempt(n) {
log(`Attempt ${n}/${retries}`);
var lis = await progressiveScan();
if (!looseDone) {
looseDone = true;
document
.querySelectorAll('img[title="Turn Egg"]')
.forEach((img) => {
log("checking img", img);
const li = img.parentElement?.parentElement?.parentElement;
const a = li?.querySelector("a.pet");
if (a) {
lis.push(a);
log("Found!");
}
});
log("Loose fallback");
if (lis.length) {
log(`Loose found ${lis.length}`);
setEggs(lis.map((a) => a.href).filter(Boolean));
return navigateEggProfile();
}
}
if (n < retries) return attempt(n + 1);
log("No eggs → next friend");
stepFI();
navigateToNextFriend();
}
attempt(1);
})();
}, 1500);
}
function navigateEggProfile() {
if (!isRunning()) return;
const eggs = getEggs(),
idx = getEI();
if (idx >= eggs.length) {
log("Eggs done for this friend");
stepFI();
navigateToNextFriend();
return;
}
log(`Go to egg page ${idx + 1}/${eggs.length}`);
location.href = eggs[idx];
}
async function solveAndSubmitAll() {
const dlgSel = 'div.ui-dialog[role="dialog"]',
turnSel = 'button[onclick*="pet_turn_egg"]',
maxR = 2;
for (let a = 1; a <= maxR; a++) {
const dlg = document.querySelector(dlgSel);
if (!dlg) continue;
const img = dlg.querySelector("fieldset img");
if (!img) {
log("No modal image");
break;
}
const url = img.src.replace(/^\/\//, "https://");
log(`Solve attempt ${a}: fetch ${url}`);
const blob = await new Promise((res, rej) =>
GM_xmlhttpRequest({
method: "GET",
url,
responseType: "blob",
onload: (r) => res(r.response),
onerror: (e) => rej(e),
})
);
const form = new FormData();
form.append("file", blob, "egg.jpg");
log("Sending to AI");
const resp = await fetch("http://127.0.0.1:8000/predict", {
method: "POST",
body: form,
});
const { predicted_class } = await resp.json();
log("Predicted", predicted_class);
Array.from(dlg.querySelectorAll("label")).forEach((lbl) => {
if (lbl.textContent.trim() === predicted_class) lbl.click();
});
dlg.querySelector(".ui-dialog-buttonpane button").click();
await new Promise((r) => setTimeout(r, 1000));
const err = Array.from(document.querySelectorAll(dlgSel)).find(
(d) => d.querySelector(".ui-dialog-title")?.innerText === "Error"
);
if (err) {
log("Error modal, retry");
err.querySelector(".ui-dialog-buttonpane button").click();
document.querySelector(turnSel).click();
await new Promise((r) => setTimeout(r, 1000));
continue;
}
break;
}
log("All solved, moving on");
stepEggIndex();
setTimeout(() => {
const prev = location.href;
navigateEggProfile();
setTimeout(() => {
if (location.href === prev) {
log("Stuck, retry nav");
navigateEggProfile();
}
}, 1500);
}, 500);
}
function handleProfile() {
if (!isRunning()) return;
log("On egg profile");
let tries = 0,
max = 6;
(function clickTry() {
const btn = document.querySelector('button[onclick*="pet_turn_egg"]');
if (btn) {
log("Click turn");
btn.click();
setTimeout(async () => {
const dlg = document.querySelector('div.ui-dialog[role="dialog"]');
if (dlg && /Name the Species/.test(dlg.innerHTML)) {
log("Puzzle");
await solveAndSubmitAll();
} else {
log("No puzzle");
stepEggIndex();
navigateEggProfile();
}
}, 800);
} else if (tries++ < max) {
setTimeout(clickTry, 200);
} else {
log("No button");
stepEggIndex();
navigateEggProfile();
}
})();
}
function main() {
if (!isRunning()) return;
const h = location.hash || "";
log("Old Across main", h);
if (h.includes("sub=profile") && h.includes("pet=")) {
handleProfile();
} else if (h.includes("sub=hatchery")) {
collectEggs();
} else {
collectFriends();
}
}
return { startBot, stopBot, resumeBot, main };
})();
// --- Old Ovipets Thorough ---
const OvipetsOldThorough = (function () {
const RUN_KEY = "ovipets_old_thorough_running";
const FR_KEY = "ovipets_old_thorough_friends";
const FI_KEY = "ovipets_old_thorough_friend_index";
const EG_KEY = "ovipets_old_thorough_eggs";
const EI_KEY = "ovipets_old_thorough_egg_index";
function log(...args) {
console.log(
"%c[OVIPETS-OLD-THOROUGH]",
"background:#666;color:#bada55;",
...args
);
}
function startBot() {
log("Starting Old Thorough bot");
sessionStorage.setItem(RUN_KEY, "true");
sessionStorage.removeItem(FR_KEY);
sessionStorage.setItem(FI_KEY, "0");
sessionStorage.removeItem(EG_KEY);
sessionStorage.setItem(EI_KEY, "0");
hideResume();
main();
}
function stopBot() {
sessionStorage.removeItem(RUN_KEY);
log("Stopped Old Thorough bot");
}
function resumeBot() {
sessionStorage.setItem(RUN_KEY, "true");
hideResume();
stepEggIndex();
navigateEggProfile();
}
function showResume() {
const btn = document.getElementById("ovipets-old-thorough-resume");
if (btn) btn.style.display = "inline-block";
}
function hideResume() {
const btn = document.getElementById("ovipets-old-thorough-resume");
if (btn) btn.style.display = "none";
}
function isRunning() {
return sessionStorage.getItem(RUN_KEY) === "true";
}
function getFriends() {
try {
return JSON.parse(sessionStorage.getItem(FR_KEY)) || [];
} catch {
return [];
}
}
function setFriends(a) {
sessionStorage.setItem(FR_KEY, JSON.stringify(a));
}
function getFI() {
return parseInt(sessionStorage.getItem(FI_KEY) || "0", 10);
}
function setFI(i) {
sessionStorage.setItem(FI_KEY, String(i));
log(`Friend index set to ${i}`);
}
function stepFI() {
setFI(getFI() + 1);
}
function getEggs() {
try {
return JSON.parse(sessionStorage.getItem(EG_KEY)) || [];
} catch {
return [];
}
}
function setEggs(a) {
sessionStorage.setItem(EG_KEY, JSON.stringify(a));
}
function getEI() {
return parseInt(sessionStorage.getItem(EI_KEY) || "0", 10);
}
function setEI(i) {
sessionStorage.setItem(EI_KEY, String(i));
}
function stepEggIndex() {
setEI(getEI() + 1);
}
function collectFriends() {
if (!isRunning()) return;
log("Collecting friends");
const ul =
document.querySelector("body div#friends-list-modal ul") ||
document.querySelector("body div.friends-list ul") ||
document.querySelector("body ul");
if (!ul) {
log("Friends list not found");
stopBot();
return;
}
const friends = Array.from(ul.querySelectorAll("a.user.avatar"))
.map((a) => a.href)
.filter(Boolean)
.filter((h) => h !== window.location.origin + window.location.hash);
log(`Found ${friends.length} friends`);
setFriends(friends);
navigateToNextFriend();
}
function navigateToNextFriend() {
if (!isRunning()) return;
const friends = getFriends();
let idx = getFI();
if (idx >= friends.length) {
log("Restarting friends at 0");
idx = 0;
setFI(0);
}
const friendUrl = friends[idx];
if (!friendUrl || typeof friendUrl !== "string") {
log(`Invalid URL at ${idx}, skipping`);
stepFI();
setTimeout(navigateToNextFriend, 500);
return;
}
let url = friendUrl.replace("#!/", "#!/?src=pets&sub=hatchery&usr=");
if (url.includes("&usr=?usr=")) url = url.replace("&usr=?usr=", "&usr=");
log(`Go to friend ${idx + 1}/${friends.length}`);
location.href = url;
}
function hideAnnoyingDiv() {
const annoyingSection = document.querySelector("#unnamed");
if (annoyingSection) {
annoyingSection.style.display = "none";
log("Hiding annoying section");
}
}
function collectEggs(retries = 3) {
if (!isRunning()) return;
setTimeout(function () {
hideAnnoyingDiv();
log("Collecting eggs (thorough scan)");
const progressiveScan = async () => {
const found = new Set();
const stepY = window.innerHeight * 0.1;
log("--- SCAN DOWN ---");
for (let y = 0; y <= document.body.scrollHeight; y += stepY) {
window.scrollTo(0, y);
}
hideAnnoyingDiv();
window.scrollTo(0, 0);
log(`Scan complete, found ${found.size} li`);
return Array.from(
document.querySelectorAll(
"#hatchery > div > form > ul > li > div > a"
)
);
};
(async () => {
let looseDone = false;
async function attempt(n) {
log(`Attempt ${n}/${retries}`);
var lis = await progressiveScan();
looseDone = true;
log("Thorough fallback");
if (lis.length) {
log(`Thorough found ${lis.length}`);
setEggs(lis.map((a) => a.href).filter(Boolean));
return navigateEggProfile();
}
if (n < retries) return attempt(n + 1);
log("No eggs → next friend");
stepFI();
navigateToNextFriend();
}
attempt(1);
})();
}, 1500);
}
function navigateEggProfile() {
if (!isRunning()) return;
const eggs = getEggs(),
idx = getEI();
if (idx >= eggs.length) {
log("Eggs done for this friend");
stepFI();
navigateToNextFriend();
return;
}
log(`Go to egg page ${idx + 1}/${eggs.length}`);
location.href = eggs[idx];
}
async function solveAndSubmitAll() {
const dlgSel = 'div.ui-dialog[role="dialog"]',
turnSel = 'button[onclick*="pet_turn_egg"]',
maxR = 2;
for (let a = 1; a <= maxR; a++) {
const dlg = document.querySelector(dlgSel);
if (!dlg) continue;
const img = dlg.querySelector("fieldset img");
if (!img) {
log("No modal image");
break;
}
const url = img.src.replace(/^\/\//, "https://");
log(`Solve attempt ${a}: fetch ${url}`);
const blob = await new Promise((res, rej) =>
GM_xmlhttpRequest({
method: "GET",
url,
responseType: "blob",
onload: (r) => res(r.response),
onerror: (e) => rej(e),
})
);
const form = new FormData();
form.append("file", blob, "egg.jpg");
log("Sending to AI");
const resp = await fetch("http://127.0.0.1:8000/predict", {
method: "POST",
body: form,
});
const { predicted_class } = await resp.json();
log("Predicted", predicted_class);
Array.from(dlg.querySelectorAll("label")).forEach((lbl) => {
if (lbl.textContent.trim() === predicted_class) lbl.click();
});
dlg.querySelector(".ui-dialog-buttonpane button").click();
await new Promise((r) => setTimeout(r, 1000));
const err = Array.from(document.querySelectorAll(dlgSel)).find(
(d) => d.querySelector(".ui-dialog-title")?.innerText === "Error"
);
if (err) {
log("Error modal, retry");
err.querySelector(".ui-dialog-buttonpane button").click();
document.querySelector(turnSel).click();
await new Promise((r) => setTimeout(r, 1000));
continue;
}
break;
}
log("All solved, moving on");
stepEggIndex();
setTimeout(() => {
const prev = location.href;
navigateEggProfile();
setTimeout(() => {
if (location.href === prev) {
log("Stuck, retry nav");
navigateEggProfile();
}
}, 1500);
}, 500);
}
function handleProfile() {
if (!isRunning()) return;
log("On egg profile");
let tries = 0,
max = 2;
(function clickTry() {
const btn = document.querySelector('button[onclick*="pet_turn_egg"]');
if (btn) {
log("Click turn");
btn.click();
setTimeout(async () => {
const dlg = document.querySelector('div.ui-dialog[role="dialog"]');
if (dlg && /Name the Species/.test(dlg.innerHTML)) {
log("Puzzle");
await solveAndSubmitAll();
} else {
log("No puzzle");
stepEggIndex();
navigateEggProfile();
}
}, 800);
} else if (tries++ < max) {
setTimeout(clickTry, 100);
} else {
log("No button");
stepEggIndex();
navigateEggProfile();
}
})();
}
function main() {
if (!isRunning()) return;
const h = location.hash || "";
log("Old Thorough main", h);
if (h.includes("sub=profile") && h.includes("pet=")) {
handleProfile();
} else if (h.includes("sub=hatchery")) {
collectEggs();
} else {
collectFriends();
}
}
return { startBot, stopBot, resumeBot, main };
})();
// --- Adopter Module ---
const Adopter = (function () {
const RUN_KEY = "adopter_running";
const PETS_KEY = "adopter_pets";
const IDX_KEY = "adopter_index";
const BASE_KEY = "adopter_base_href";
const UL_KEY = "adopter_selected_ul";
function log(...args) {
console.log("%c[ADOPTER]", "background:#ff6600;color:#fff;", ...args);
}
function startBot() {
log(
"Starting Adopter - Please click on the UL element containing the pets"
);
// Store the current location as base
sessionStorage.setItem(BASE_KEY, location.href);
sessionStorage.setItem(RUN_KEY, "true");
sessionStorage.setItem(IDX_KEY, "0");
sessionStorage.removeItem(PETS_KEY);
sessionStorage.removeItem(UL_KEY);
// Enable UL selection mode
enableULSelection();
}
function stopBot() {
sessionStorage.removeItem(RUN_KEY);
sessionStorage.removeItem(UL_KEY);
disableULSelection();
log("Stopped Adopter");
}
function isRunning() {
return sessionStorage.getItem(RUN_KEY) === "true";
}
function getBase() {
return sessionStorage.getItem(BASE_KEY);
}
function getPets() {
try {
return JSON.parse(sessionStorage.getItem(PETS_KEY)) || [];
} catch {
return [];
}
}
function savePets(pets) {
sessionStorage.setItem(PETS_KEY, JSON.stringify(pets));
}
function getIndex() {
return parseInt(sessionStorage.getItem(IDX_KEY) || "0", 10);
}
function setIndex(i) {
sessionStorage.setItem(IDX_KEY, String(i));
}
function getSelectedUL() {
return sessionStorage.getItem(UL_KEY);
}
function setSelectedUL(selector) {
sessionStorage.setItem(UL_KEY, selector);
}
function enableULSelection() {
// Create visual indicators
const style = document.createElement("style");
style.id = "adopter-ul-selection-style";
style.textContent = `
.adopter-ul-highlight {
outline: 3px solid #ff6600 !important;
outline-offset: 2px !important;
cursor: pointer !important;
position: relative !important;
}
.adopter-ul-highlight::before {
content: "Click to select this list for adoption";
position: absolute;
top: -25px;
left: 0;
background: #ff6600;
color: white;
padding: 2px 8px;
font-size: 12px;
border-radius: 3px;
white-space: nowrap;
z-index: 10000;
}
.adopter-selection-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0,0,0,0.3);
z-index: 9999;
pointer-events: none;
}
.adopter-instruction {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: #ff6600;
color: white;
padding: 20px;
border-radius: 8px;
font-family: monospace;
font-size: 16px;
z-index: 10001;
text-align: center;
box-shadow: 0 4px 20px rgba(0,0,0,0.5);
}
`;
document.head.appendChild(style);
// Create overlay
const overlay = document.createElement("div");
overlay.className = "adopter-selection-overlay";
overlay.id = "adopter-overlay";
document.body.appendChild(overlay);
// Create instruction
const instruction = document.createElement("div");
instruction.className = "adopter-instruction";
instruction.id = "adopter-instruction";
instruction.innerHTML = `
<div style="margin-bottom: 10px;"><strong>🏠 Adopter Setup</strong></div>
<div>Hover over UL elements and click the one containing the pets you want to adopt</div>
<div style="margin-top: 10px; font-size: 12px;">Press ESC to cancel</div>
`;
document.body.appendChild(instruction);
// Find all UL elements and add hover effects
const uls = document.querySelectorAll("ul");
uls.forEach((ul) => {
ul.addEventListener("mouseenter", handleULHover);
ul.addEventListener("mouseleave", handleULLeave);
ul.addEventListener("click", handleULClick);
});
// ESC to cancel
document.addEventListener("keydown", handleEscapeKey);
log(
`Found ${uls.length} UL elements - hover to highlight, click to select`
);
}
function disableULSelection() {
// Remove style
const style = document.getElementById("adopter-ul-selection-style");
if (style) style.remove();
// Remove overlay and instruction
const overlay = document.getElementById("adopter-overlay");
if (overlay) overlay.remove();
const instruction = document.getElementById("adopter-instruction");
if (instruction) instruction.remove();
// Remove event listeners from all ULs
const uls = document.querySelectorAll("ul");
uls.forEach((ul) => {
ul.removeEventListener("mouseenter", handleULHover);
ul.removeEventListener("mouseleave", handleULLeave);
ul.removeEventListener("click", handleULClick);
ul.classList.remove("adopter-ul-highlight");
});
document.removeEventListener("keydown", handleEscapeKey);
}
function handleULHover(event) {
event.target.classList.add("adopter-ul-highlight");
}
function handleULLeave(event) {
event.target.classList.remove("adopter-ul-highlight");
}
function handleULClick(event) {
event.preventDefault();
event.stopPropagation();
const ul = event.target;
// Remove highlight class before generating selector
ul.classList.remove("adopter-ul-highlight");
// Generate a unique selector for this UL
let selector = generateUniqueSelector(ul);
log(`Selected UL with selector: ${selector}`);
// Test the selector immediately
const testElement = document.querySelector(selector);
if (testElement === ul) {
log("✅ Selector test passed");
} else {
log("❌ Selector test failed, trying alternative...");
// Try a more specific approach
if (ul.parentElement) {
const parent = ul.parentElement;
if (parent.id) {
selector = `#${parent.id} ul:nth-child(${
Array.from(parent.children).indexOf(ul) + 1
})`;
} else if (parent.className) {
const classes = parent.className.split(" ").filter((c) => c.trim());
selector = `.${classes.join(".")} ul:nth-child(${
Array.from(parent.children).indexOf(ul) + 1
})`;
} else {
// Ultimate fallback - store the innerHTML signature
const signature = ul.innerHTML.substring(0, 100);
selector = `ul-signature:${btoa(signature)}`;
// Store the actual element reference temporarily
window.adopterSelectedUL = ul;
log("Using element reference fallback");
}
}
log(`Alternative selector: ${selector}`);
}
setSelectedUL(selector);
// Clean up selection mode
disableULSelection();
// Start collecting pets from the selected UL
setTimeout(() => {
collectPets();
}, 300);
}
function handleEscapeKey(event) {
if (event.key === "Escape") {
log("Selection cancelled");
stopBot();
}
}
function generateUniqueSelector(element) {
// Remove the highlight class before generating selector
element.classList.remove("adopter-ul-highlight");
// Try ID first
if (element.id) {
return `#${element.id}`;
}
// Try class combination (excluding our highlight class)
if (element.className) {
const classes = element.className
.split(" ")
.filter((c) => c.trim() && c !== "adopter-ul-highlight");
if (classes.length > 0) {
let selector = `ul.${classes.join(".")}`;
if (document.querySelectorAll(selector).length === 1) {
return selector;
}
}
}
// Try parent-child relationship
let parent = element.parentElement;
if (parent) {
if (parent.id) {
return `#${parent.id} > ul`;
}
if (parent.className) {
const classes = parent.className.split(" ").filter((c) => c.trim());
if (classes.length > 0) {
let selector = `.${classes.join(".")} > ul`;
if (document.querySelectorAll(selector).length === 1) {
return selector;
}
}
}
}
// Try to find position among siblings
const siblings = Array.from(element.parentElement?.children || []);
const ulSiblings = siblings.filter((el) => el.tagName === "UL");
if (ulSiblings.length === 1) {
// Only UL child
if (parent && parent.id) {
return `#${parent.id} ul`;
}
if (parent && parent.className) {
const classes = parent.className.split(" ").filter((c) => c.trim());
if (classes.length > 0) {
return `.${classes.join(".")} ul`;
}
}
} else {
// Multiple UL siblings, use nth-of-type
const index = ulSiblings.indexOf(element) + 1;
if (parent && parent.id) {
return `#${parent.id} ul:nth-of-type(${index})`;
}
if (parent && parent.className) {
const classes = parent.className.split(" ").filter((c) => c.trim());
if (classes.length > 0) {
return `.${classes.join(".")} ul:nth-of-type(${index})`;
}
}
}
// Final fallback: use XPath-like approach
function getElementPath(el) {
if (el.id) return `#${el.id}`;
if (el === document.body) return "body";
const parent = el.parentElement;
if (!parent) return el.tagName.toLowerCase();
const siblings = Array.from(parent.children);
const sameTagSiblings = siblings.filter(
(sibling) => sibling.tagName === el.tagName
);
if (sameTagSiblings.length === 1) {
return `${getElementPath(parent)} > ${el.tagName.toLowerCase()}`;
} else {
const index = sameTagSiblings.indexOf(el) + 1;
return `${getElementPath(
parent
)} > ${el.tagName.toLowerCase()}:nth-of-type(${index})`;
}
}
return getElementPath(element);
}
function collectPets() {
if (!isRunning()) return;
log("Collecting adoption pets from selected UL");
const ulSelector = getSelectedUL();
if (!ulSelector) {
log("No UL selected");
stopBot();
return;
}
let ul;
// Handle special fallback selector
if (ulSelector.startsWith("ul-signature:")) {
ul = window.adopterSelectedUL;
if (!ul) {
log("Element reference lost");
stopBot();
return;
}
} else {
ul = document.querySelector(ulSelector);
}
if (!ul) {
log(`Selected UL not found: ${ulSelector}`);
stopBot();
return;
}
log(`Found UL element with ${ul.children.length} child elements`);
// Extract all pet links from li elements
const petLinks = [];
const lis = ul.querySelectorAll("li");
log(`Scanning ${lis.length} li elements for pet links...`);
lis.forEach((li, index) => {
const petLink =
li.querySelector('a.pet[href*="pet="]') ||
li.querySelector('a[href*="pet="]') ||
li.querySelector('a[href*="sub=profile"]');
if (petLink && petLink.href) {
petLinks.push(petLink.href);
log(`Found pet link ${index + 1}: ${petLink.href}`);
}
});
if (!petLinks.length) {
log("No adoption pets found in selected UL");
log("UL HTML preview:", ul.innerHTML.substring(0, 500));
stopBot();
return;
}
log(`Found ${petLinks.length} adoption pets`);
savePets(petLinks);
goToPet();
}
function goToPet() {
if (!isRunning()) return;
const pets = getPets();
const idx = getIndex();
if (idx >= pets.length) {
log("All pets processed - adoption complete!");
stopBot();
location.href = getBase();
return;
}
log(`🏠 Going to adopt pet ${idx + 1}/${pets.length}...`);
location.href = pets[idx];
}
function handlePetProfile() {
if (!isRunning()) return;
log("🔍 Looking for adoption button on pet profile...");
let tries = 0;
const maxTries = 10;
const tryAdoptButton = () => {
// Look for adoption button
let adoptBtn = null;
// Method 1: Look for button with "adopt" text
const buttons = document.querySelectorAll(
'button, input[type="button"], input[type="submit"]'
);
for (let btn of buttons) {
if (
btn.textContent.toLowerCase().includes("adopt") ||
btn.value?.toLowerCase().includes("adopt") ||
btn.onclick?.toString().includes("adopt")
) {
adoptBtn = btn;
break;
}
}
// Method 2: Look for links with adopt in onclick
if (!adoptBtn) {
const links = document.querySelectorAll(
'a[onclick*="adopt"], a[href*="adopt"]'
);
if (links.length > 0) {
adoptBtn = links[0];
}
}
if (adoptBtn) {
log("✅ Found adoption button, clicking...");
adoptBtn.click();
// Wait for adoption modal or confirmation
setTimeout(() => {
if (isRunning()) {
handleAdoptionModal();
}
}, 300);
} else if (tries++ < maxTries) {
log(
`⏳ Adoption button not found, retrying... (${tries}/${maxTries})`
);
setTimeout(tryAdoptButton, 300);
} else {
log("❌ No adoption button found, moving to next pet");
setIndex(getIndex() + 1);
setTimeout(() => {
if (isRunning()) {
goToPet();
}
}, 300);
}
};
// Initial delay to let page load
setTimeout(tryAdoptButton, 300);
}
function handleAdoptionModal() {
if (!isRunning()) return;
log("🔍 Looking for adoption confirmation modal...");
let modalTries = 0;
const maxModalTries = 10;
const tryModal = () => {
let confirmBtn = null;
// Method 1: Look for visible dialog with adoption confirmation
const modals = document.querySelectorAll(
'div.ui-dialog[role="dialog"]'
);
for (let modal of modals) {
if (modal.style.display !== "none" && modal.offsetParent !== null) {
// Check if this modal contains adoption-related content
if (
modal.innerHTML.includes("adopt") ||
modal.innerHTML.includes("Adopt")
) {
const buttonPane = modal.querySelector(".ui-dialog-buttonpane");
if (buttonPane) {
confirmBtn = buttonPane.querySelector("button:first-child");
if (confirmBtn) {
log("Found adoption modal confirmation button");
break;
}
}
}
}
}
// Method 2: Look for any visible dialog button as fallback
if (!confirmBtn) {
const dialogButtons = document.querySelectorAll(
".ui-dialog-buttonpane button"
);
for (let btn of dialogButtons) {
if (btn.offsetParent !== null) {
// visible
confirmBtn = btn;
log("Found dialog button via visibility fallback");
break;
}
}
}
if (confirmBtn) {
log("✅ Found adoption confirmation button, clicking...");
confirmBtn.click();
log("🏠 Adoption confirmed! Moving to next pet...");
// Move to next pet
setTimeout(() => {
if (isRunning()) {
setIndex(getIndex() + 1);
goToPet();
}
}, 300);
} else if (modalTries++ < maxModalTries) {
log(
`⏳ Modal button not found, retrying... (${modalTries}/${maxModalTries})`
);
setTimeout(tryModal, 300);
} else {
log("❌ No modal confirmation button found, moving to next pet");
setIndex(getIndex() + 1);
setTimeout(() => {
if (isRunning()) {
goToPet();
}
}, 300);
}
};
tryModal();
}
function main() {
if (!isRunning()) return;
const href = location.href;
const base = getBase();
log("🚀 Adopter main executing...", href);
// If we're on a pet profile page
if (href.includes("sub=profile") && href.includes("pet=")) {
log("📍 On pet profile, looking for adoption button");
setTimeout(() => {
if (isRunning()) {
handlePetProfile();
}
}, 300);
}
// If we're back at the base page and haven't collected pets yet
else if (href === base && !sessionStorage.getItem(PETS_KEY)) {
log("📍 At base, need to collect pets");
setTimeout(() => {
if (isRunning()) {
collectPets();
}
}, 300);
}
// If we're back at the base page and have pets collected
else if (href === base) {
log("📍 At base, have pets, continuing...");
setTimeout(() => {
if (isRunning()) {
goToPet();
}
}, 300);
}
// If we're on a different page
else {
const pets = getPets();
if (pets.length > 0) {
log("📍 On different page, going to next pet");
setTimeout(() => {
if (isRunning()) {
goToPet();
}
}, 300);
} else {
log("📍 No pets collected, returning to base");
location.href = base;
}
}
}
return { startBot, stopBot, main };
})();
// --- Breeder Module ---
const Breeder = (function () {
const RUN_KEY = "breeder_running";
const PETS_KEY = "breeder_pets";
const IDX_KEY = "breeder_index";
const BASE_KEY = "breeder_base_href";
const UL_KEY = "breeder_selected_ul";
const JUST_BREAD = "breeder_just_bred";
function log(...args) {
console.log("%c[BREEDER]", "background:#ff1493;color:#fff;", ...args);
}
function startBot() {
log(
"Starting Breeder - Please click on the UL element containing the pets"
);
// Store the current location as base
sessionStorage.setItem(BASE_KEY, location.href);
sessionStorage.setItem(RUN_KEY, "true");
sessionStorage.setItem(IDX_KEY, "0");
sessionStorage.setItem(JUST_BREAD, "false");
sessionStorage.removeItem(PETS_KEY);
sessionStorage.removeItem(UL_KEY);
// Enable UL selection mode
enableULSelection();
}
function stopBot() {
sessionStorage.removeItem(RUN_KEY);
sessionStorage.removeItem(UL_KEY);
sessionStorage.removeItem(JUST_BREAD);
disableULSelection();
log("Stopped Breeder");
}
function isRunning() {
return sessionStorage.getItem(RUN_KEY) === "true";
}
function getBase() {
return sessionStorage.getItem(BASE_KEY);
}
function getPets() {
try {
return JSON.parse(sessionStorage.getItem(PETS_KEY)) || [];
} catch {
return [];
}
}
function savePets(pets) {
sessionStorage.setItem(PETS_KEY, JSON.stringify(pets));
}
function getIndex() {
return parseInt(sessionStorage.getItem(IDX_KEY) || "0", 10);
}
function setIndex(i) {
sessionStorage.setItem(IDX_KEY, String(i));
}
function getSelectedUL() {
return sessionStorage.getItem(UL_KEY);
}
function setSelectedUL(selector) {
sessionStorage.setItem(UL_KEY, selector);
}
function enableULSelection() {
// Create visual indicators
const style = document.createElement("style");
style.id = "breeder-ul-selection-style";
style.textContent = `
.breeder-ul-highlight {
outline: 3px solid #ff1493 !important;
outline-offset: 2px !important;
cursor: pointer !important;
position: relative !important;
}
.breeder-ul-highlight::before {
content: "Click to select this list for breeding";
position: absolute;
top: -25px;
left: 0;
background: #ff1493;
color: white;
padding: 2px 8px;
font-size: 12px;
border-radius: 3px;
white-space: nowrap;
z-index: 10000;
}
.breeder-selection-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0,0,0,0.3);
z-index: 9999;
pointer-events: none;
}
.breeder-instruction {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: #ff1493;
color: white;
padding: 20px;
border-radius: 8px;
font-family: monospace;
font-size: 16px;
z-index: 10001;
text-align: center;
box-shadow: 0 4px 20px rgba(0,0,0,0.5);
}
`;
document.head.appendChild(style);
// Create overlay
const overlay = document.createElement("div");
overlay.className = "breeder-selection-overlay";
overlay.id = "breeder-overlay";
document.body.appendChild(overlay);
// Create instruction
const instruction = document.createElement("div");
instruction.className = "breeder-instruction";
instruction.id = "breeder-instruction";
instruction.innerHTML = `
<div style="margin-bottom: 10px;"><strong>💕 Breeder Setup</strong></div>
<div>Hover over UL elements and click the one containing the pets you want to breed</div>
<div style="margin-top: 10px; font-size: 12px;">Press ESC to cancel</div>
`;
document.body.appendChild(instruction);
// Find all UL elements and add hover effects
const uls = document.querySelectorAll("ul");
uls.forEach((ul) => {
ul.addEventListener("mouseenter", handleULHover);
ul.addEventListener("mouseleave", handleULLeave);
ul.addEventListener("click", handleULClick);
});
// ESC to cancel
document.addEventListener("keydown", handleEscapeKey);
log(
`Found ${uls.length} UL elements - hover to highlight, click to select`
);
}
function disableULSelection() {
// Remove style
const style = document.getElementById("breeder-ul-selection-style");
if (style) style.remove();
// Remove overlay and instruction
const overlay = document.getElementById("breeder-overlay");
if (overlay) overlay.remove();
const instruction = document.getElementById("breeder-instruction");
if (instruction) instruction.remove();
// Remove event listeners from all ULs
const uls = document.querySelectorAll("ul");
uls.forEach((ul) => {
ul.removeEventListener("mouseenter", handleULHover);
ul.removeEventListener("mouseleave", handleULLeave);
ul.removeEventListener("click", handleULClick);
ul.classList.remove("breeder-ul-highlight");
});
document.removeEventListener("keydown", handleEscapeKey);
}
function handleULHover(event) {
event.target.classList.add("breeder-ul-highlight");
}
function handleULLeave(event) {
event.target.classList.remove("breeder-ul-highlight");
}
function handleULClick(event) {
event.preventDefault();
event.stopPropagation();
const ul = event.target;
// Remove highlight class before generating selector
ul.classList.remove("breeder-ul-highlight");
// Generate a unique selector for this UL
let selector = generateUniqueSelector(ul);
log(`Selected UL with selector: ${selector}`);
// Test the selector immediately
const testElement = document.querySelector(selector);
if (testElement === ul) {
log("✅ Selector test passed");
} else {
log("❌ Selector test failed, trying alternative...");
// Try a more specific approach
if (ul.parentElement) {
const parent = ul.parentElement;
if (parent.id) {
selector = `#${parent.id} ul:nth-child(${
Array.from(parent.children).indexOf(ul) + 1
})`;
} else if (parent.className) {
const classes = parent.className.split(" ").filter((c) => c.trim());
selector = `.${classes.join(".")} ul:nth-child(${
Array.from(parent.children).indexOf(ul) + 1
})`;
} else {
// Ultimate fallback - store the innerHTML signature
const signature = ul.innerHTML.substring(0, 100);
selector = `ul-signature:${btoa(signature)}`;
// Store the actual element reference temporarily
window.breederSelectedUL = ul;
log("Using element reference fallback");
}
}
log(`Alternative selector: ${selector}`);
}
setSelectedUL(selector);
// Clean up selection mode
disableULSelection();
// Start collecting pets from the selected UL
setTimeout(() => {
collectPets();
}, 300);
}
function handleEscapeKey(event) {
if (event.key === "Escape") {
log("Selection cancelled");
stopBot();
}
}
function generateUniqueSelector(element) {
// Remove the highlight class before generating selector
element.classList.remove("breeder-ul-highlight");
// Try ID first
if (element.id) {
return `#${element.id}`;
}
// Try class combination (excluding our highlight class)
if (element.className) {
const classes = element.className
.split(" ")
.filter((c) => c.trim() && c !== "breeder-ul-highlight");
if (classes.length > 0) {
let selector = `ul.${classes.join(".")}`;
if (document.querySelectorAll(selector).length === 1) {
return selector;
}
}
}
// Try parent-child relationship
let parent = element.parentElement;
if (parent) {
if (parent.id) {
return `#${parent.id} > ul`;
}
if (parent.className) {
const classes = parent.className.split(" ").filter((c) => c.trim());
if (classes.length > 0) {
let selector = `.${classes.join(".")} > ul`;
if (document.querySelectorAll(selector).length === 1) {
return selector;
}
}
}
}
// Try to find position among siblings
const siblings = Array.from(element.parentElement?.children || []);
const ulSiblings = siblings.filter((el) => el.tagName === "UL");
if (ulSiblings.length === 1) {
// Only UL child
if (parent && parent.id) {
return `#${parent.id} ul`;
}
if (parent && parent.className) {
const classes = parent.className.split(" ").filter((c) => c.trim());
if (classes.length > 0) {
return `.${classes.join(".")} ul`;
}
}
} else {
// Multiple UL siblings, use nth-of-type
const index = ulSiblings.indexOf(element) + 1;
if (parent && parent.id) {
return `#${parent.id} ul:nth-of-type(${index})`;
}
if (parent && parent.className) {
const classes = parent.className.split(" ").filter((c) => c.trim());
if (classes.length > 0) {
return `.${classes.join(".")} ul:nth-of-type(${index})`;
}
}
}
// Final fallback: use XPath-like approach
function getElementPath(el) {
if (el.id) return `#${el.id}`;
if (el === document.body) return "body";
const parent = el.parentElement;
if (!parent) return el.tagName.toLowerCase();
const siblings = Array.from(parent.children);
const sameTagSiblings = siblings.filter(
(sibling) => sibling.tagName === el.tagName
);
if (sameTagSiblings.length === 1) {
return `${getElementPath(parent)} > ${el.tagName.toLowerCase()}`;
} else {
const index = sameTagSiblings.indexOf(el) + 1;
return `${getElementPath(
parent
)} > ${el.tagName.toLowerCase()}:nth-of-type(${index})`;
}
}
return getElementPath(element);
}
function collectPets() {
if (!isRunning()) return;
log("Collecting breeding pets from selected UL");
const ulSelector = getSelectedUL();
if (!ulSelector) {
log("No UL selected");
stopBot();
return;
}
let ul;
// Handle special fallback selector
if (ulSelector.startsWith("ul-signature:")) {
ul = window.breederSelectedUL;
if (!ul) {
log("Element reference lost");
stopBot();
return;
}
} else {
ul = document.querySelector(ulSelector);
}
if (!ul) {
log(`Selected UL not found: ${ulSelector}`);
stopBot();
return;
}
log(`Found UL element with ${ul.children.length} child elements`);
// Extract all pet links from li elements
const petLinks = [];
const lis = ul.querySelectorAll("li");
log(`Scanning ${lis.length} li elements for pet links...`);
lis.forEach((li, index) => {
const petLink =
li.querySelector('a.pet[href*="pet="]') ||
li.querySelector('a[href*="pet="]') ||
li.querySelector('a[href*="sub=profile"]');
if (petLink && petLink.href) {
petLinks.push(petLink.href);
log(`Found pet link ${index + 1}: ${petLink.href}`);
}
});
if (!petLinks.length) {
log("No breeding pets found in selected UL");
log("UL HTML preview:", ul.innerHTML.substring(0, 500));
stopBot();
return;
}
log(`Found ${petLinks.length} breeding pets`);
savePets(petLinks);
goToPet();
}
function goToPet() {
if (!isRunning()) return;
const pets = getPets();
const idx = getIndex();
if (idx >= pets.length) {
log("All pets processed - breeding complete!");
stopBot();
location.href = getBase();
return;
}
log(`🐾 Going to pet ${idx + 1}/${pets.length}...`);
location.href = pets[idx];
}
function handlePetProfile() {
if (!isRunning()) return;
log("🔍 Looking for breeding tab on pet profile...");
let tries = 0;
const maxTries = 1; // Increased wait time
const tryBreedingTab = () => {
if (!isRunning()) return;
// Try multiple selectors for the breeding tab
let breedingTab = null;
// Method 1: Look for the specific li with breeding panel attribute
const breedingLi = document.querySelector(
'#tabs > div > form > div > ul > li[panel*="sec=breeding"]'
);
if (breedingLi) {
breedingTab = breedingLi.querySelector("a.ui-tabs-anchor");
}
// Method 2: Look for tab with "Breeding" text
if (!breedingTab) {
const tabs = document.querySelectorAll("#tabs a.ui-tabs-anchor");
for (let tab of tabs) {
if (tab.textContent.trim().toLowerCase().includes("breeding")) {
breedingTab = tab;
break;
}
}
}
// Method 3: Try the specific selector you provided
if (!breedingTab) {
breedingTab = document.querySelector("#ui-id-35");
}
// Method 4: Look for any tab with breeding in the href
if (!breedingTab) {
const allTabs = document.querySelectorAll(
'#tabs a[href*="breeding"], #tabs a[href*="Breeding"]'
);
if (allTabs.length > 0) {
breedingTab = allTabs[0];
}
}
if (breedingTab) {
log("✅ Found breeding tab, clicking...");
breedingTab.click();
// Wait longer for breeding section to load
setTimeout(() => {
if (isRunning()) {
handleBreedingSection();
}
}, 300); // Increased wait time
} else if (tries++ < maxTries) {
log(`⏳ Breeding tab not found, retrying... (${tries}/${maxTries})`);
setTimeout(tryBreedingTab, 300); // Wait longer between retries
} else {
log("❌ No breeding tab found after all attempts, skipping this pet");
setIndex(getIndex() + 1);
setTimeout(() => {
if (isRunning()) {
goToPet();
}
}, 300);
}
};
// Initial delay to let page load
setTimeout(tryBreedingTab, 300);
}
// Replace the handleBreedingModal function in the Breeder module:
function handleBreedingModal() {
if (!isRunning()) return;
log("🔍 Looking for breeding confirmation modal...");
let modalTries = 0;
const maxModalTries = 15;
const tryModal = () => {
if (!isRunning()) return;
let confirmBtn = null;
// Method 1: Look for the specific breeding modal button
confirmBtn = document.querySelector(
"body > div.ui-dialog.ui-corner-all.ui-widget.ui-widget-content.ui-front.ui-dialog-buttons.ui-draggable.ui-resizable > div.ui-dialog-buttonpane.ui-widget-content.ui-helper-clearfix > div > button:nth-child(1)"
);
// Method 2: Look for any visible dialog with breeding confirmation
if (!confirmBtn) {
const modals = document.querySelectorAll(
'div.ui-dialog[role="dialog"]'
);
for (let modal of modals) {
if (modal.style.display !== "none" && modal.offsetParent !== null) {
// Check if this modal contains breeding-related content
if (
modal.innerHTML.includes("breed") ||
modal.innerHTML.includes("Breed")
) {
const buttonPane = modal.querySelector(".ui-dialog-buttonpane");
if (buttonPane) {
confirmBtn = buttonPane.querySelector("button:first-child");
if (confirmBtn) {
log(
"Found breeding modal confirmation button via content search"
);
break;
}
}
}
}
}
}
// Method 3: Look for any visible dialog button as fallback
if (!confirmBtn) {
const dialogButtons = document.querySelectorAll(
".ui-dialog-buttonpane button"
);
for (let btn of dialogButtons) {
if (btn.offsetParent !== null) {
// visible
confirmBtn = btn;
log("Found dialog button via visibility fallback");
break;
}
}
}
if (confirmBtn) {
log("✅ Found breeding confirmation button, clicking...");
// set just bred flag
sessionStorage.setItem(JUST_BREAD, "true");
confirmBtn.click();
log(
"💕 Breeding confirmed! Staying on same pet to find more partners..."
);
} else if (modalTries++ < maxModalTries) {
log(
`⏳ Modal button not found, retrying... (${modalTries}/${maxModalTries})`
);
setTimeout(tryModal, 300);
} else {
log("❌ No modal confirmation button found, moving to next pet");
setIndex(getIndex() + 1);
setTimeout(() => {
if (isRunning()) {
goToPet();
}
}, 300);
}
};
tryModal();
}
// Also update the handleBreedingSection function to handle "no more partners" case:
function handleBreedingSection() {
if (!isRunning()) return;
log("💕 Looking for breeding partners...");
let attempts = 0;
const maxAttempts = 1;
const findBreedingPartners = () => {
if (!isRunning()) return;
// look for a feeding link
/*
<button type="button" onclick="var self = this; ui_action_cmdExec('pet_feed','PetID=469843813',self.form,function(){ui_action_secLoad('pets','profile','profile','pet=469843813&prio=1','',self);});" class="ui-button ui-corner-all ui-widget"><span>Feed</span></button>
*/
// wait 100 ms
setTimeout(() => {
if (!isRunning()) return;
}, 100);
let feedingButton = document.querySelector(
'button[onclick*="pet_feed"]'
);
if (feedingButton) {
log("✅ Found feeding button, clicking...");
feedingButton.click();
}
// find this selector
// <select name="enclosure" onchange="var self = this; ui_action_secLoad('pets','profile','breeding','usr=5262473&pet=476473623','',self);"><option value="0">hate these thing </option><option value="1">aye </option></select>
// then for each item in the select, select the item, then check the breeding section
log("🔍 looking for breeding tabs");
var breedingSelector = document.querySelector(
'select[name="enclosure"]'
);
var breedingLinks = [];
if (breedingSelector) {
log("✅ Found breeding selector, iterating options...");
const options = breedingSelector.querySelectorAll("option");
options.forEach((option) => {
option.selected = true;
log(`🔄 Selected breeding option: ${option.textContent}`);
});
// Try multiple selectors for the breeding UL
let breedingUL = document.querySelector(
"#breeding > div > form > ul"
);
if (!breedingUL) {
// Fallback: look for any UL with breeding-related links
const allULs = document.querySelectorAll("ul");
for (let ul of allULs) {
if (ul.querySelector('a[onclick*="pet_breed"]')) {
breedingUL = ul;
log("Found breeding UL via fallback search");
break;
}
}
}
if (!breedingUL && attempts++ < maxAttempts) {
log(
`⏳ Breeding section not loaded yet, retrying... (${attempts}/${maxAttempts})`
);
setTimeout(findBreedingPartners, 300);
return;
}
if (!breedingUL) {
log("❌ No breeding UL found, moving to next pet");
setIndex(getIndex() + 1);
setTimeout(() => {
if (isRunning()) {
goToPet();
}
}, 300);
return;
}
// Look for available breeding partners
breedingLinks = breedingUL.querySelectorAll(
'a[onclick*="pet_breed"]'
);
if (!breedingLinks.length) {
log(
"✅ No more breeding partners available for this pet, moving to next pet"
);
setIndex(getIndex() + 1);
setTimeout(() => {
if (isRunning()) {
goToPet();
}
}, 300);
return;
}
} else {
// Try multiple selectors for the breeding UL
let breedingUL = document.querySelector(
"#breeding > div > form > ul"
);
if (!breedingUL) {
// Fallback: look for any UL with breeding-related links
const allULs = document.querySelectorAll("ul");
for (let ul of allULs) {
if (ul.querySelector('a[onclick*="pet_breed"]')) {
breedingUL = ul;
log("Found breeding UL via fallback search");
break;
}
}
}
if (!breedingUL && attempts++ < maxAttempts) {
log(
`⏳ Breeding section not loaded yet, retrying... (${attempts}/${maxAttempts})`
);
setTimeout(findBreedingPartners, 300);
return;
}
if (!breedingUL) {
log("❌ No breeding UL found, moving to next pet");
setIndex(getIndex() + 1);
setTimeout(() => {
if (isRunning()) {
goToPet();
}
}, 300);
return;
}
// Look for available breeding partners
const breedingLinks = breedingUL.querySelectorAll(
'a[onclick*="pet_breed"]'
);
if (!breedingLinks.length) {
log(
"✅ No more breeding partners available for this pet, moving to next pet"
);
setIndex(getIndex() + 1);
setTimeout(() => {
if (isRunning()) {
goToPet();
}
}, 300);
return;
}
}
// Process the first available breeding partner
const firstBreedingLink = breedingLinks[0];
log(
`🎯 Found ${breedingLinks.length} breeding partners, clicking first one...`
);
firstBreedingLink.click();
// Wait for modal to appear
setTimeout(() => {
if (isRunning()) {
handleBreedingModal();
}
}, 300);
if (breedingLinks.length > 1) {
log(
`🔄 Found ${
breedingLinks.length - 1
} more breeding partners, will continue after this one`
);
// to repeat this pet after breeding, set the index back once
setIndex(getIndex() - 1);
}
};
findBreedingPartners();
}
function main() {
if (!isRunning()) return;
const href = location.href;
const base = getBase();
var counter = 0;
let feedingButton = document.querySelector('button[onclick*="pet_feed"]');
if (feedingButton) {
log("✅ Found feeding button, clicking...");
feedingButton.click();
}
log("🚀 Breeder main executing...", href);
// if just bred, go back to breeding section
if (sessionStorage.getItem(JUST_BREAD) === "true") {
log("🔙 Just bred, going back to breeding section...");
// set just bred flag to false
if (counter > 0) {
counter = 0;
sessionStorage.setItem(JUST_BREAD, "false");
setTimeout(() => {
if (isRunning()) {
history.back();
}
}, 300);
setTimeout(() => {
if (isRunning()) {
handlePetProfile();
}
}, 400);
} else {
counter++;
}
}
// If we're on a pet profile page
if (href.includes("sub=profile") && href.includes("pet=")) {
// Check if we're already on the breeding tab
let feedingButton = document.querySelector(
'button[onclick*="pet_feed"]'
);
if (feedingButton) {
log("✅ Found feeding button, clicking...");
feedingButton.click();
}
const breedingSection = document.querySelector("#breeding");
if (breedingSection) {
log("📍 Already on breeding section, looking for partners...");
setTimeout(() => {
if (isRunning()) {
handleBreedingSection();
}
}, 300);
} else {
let feedingButton = document.querySelector(
'button[onclick*="pet_feed"]'
);
if (feedingButton) {
log("✅ Found feeding button, clicking...");
feedingButton.click();
}
log("📍 On pet profile, need to click breeding tab");
setTimeout(() => {
if (isRunning()) {
handlePetProfile();
}
}, 300);
}
}
// If we're back at the base page and haven't collected pets yet
else if (href === base && !sessionStorage.getItem(PETS_KEY)) {
log("📍 At base, need to collect pets");
setTimeout(() => {
if (isRunning()) {
collectPets();
}
}, 300);
}
// If we're back at the base page and have pets collected
else if (href === base) {
log("📍 At base, have pets, continuing...");
setTimeout(() => {
if (isRunning()) {
goToPet();
}
}, 300);
}
// If we're on a different page, go to current pet (not next!)
else {
const pets = getPets();
if (pets.length > 0) {
log("📍 On different page, going to current pet");
setTimeout(() => {
if (isRunning()) {
// Don't increment index - go to current pet
const currentPetUrl = pets[getIndex()];
if (currentPetUrl) {
log(`🔄 Returning to current pet: ${currentPetUrl}`);
location.href = currentPetUrl;
} else {
goToPet();
}
}
}, 300);
} else {
log("📍 No pets collected, returning to base");
location.href = base;
}
}
}
return { startBot, stopBot, main };
})();
// --- Namer Module ---
const Namer = (function () {
const RUN_KEY = "namer_running";
const PETS_KEY = "namer_pets";
const IDX_KEY = "namer_index";
const BASE_KEY = "namer_base_href";
const UL_KEY = "namer_selected_ul";
const NAME = "namer_name";
function log(...args) {
console.log("%c[NAMER]", "background:#9932cc;color:#fff;", ...args);
}
function startBot() {
log(
"Starting Namer - Please click on the UL element containing the pets to name"
);
// Store the current location as base
sessionStorage.setItem(BASE_KEY, location.href);
sessionStorage.setItem(RUN_KEY, "true");
sessionStorage.setItem(IDX_KEY, "0");
sessionStorage.removeItem(PETS_KEY);
sessionStorage.removeItem(UL_KEY);
sessionStorage.removeItem(NAME);
// Enable UL selection mode
enableULSelection();
}
function stopBot() {
sessionStorage.removeItem(RUN_KEY);
sessionStorage.removeItem(UL_KEY);
disableULSelection();
log("Stopped Namer");
}
function isRunning() {
return sessionStorage.getItem(RUN_KEY) === "true";
}
function getBase() {
return sessionStorage.getItem(BASE_KEY);
}
function getPets() {
try {
return JSON.parse(sessionStorage.getItem(PETS_KEY)) || [];
} catch {
return [];
}
}
function savePets(pets) {
sessionStorage.setItem(PETS_KEY, JSON.stringify(pets));
}
function getIndex() {
return parseInt(sessionStorage.getItem(IDX_KEY) || "0", 10);
}
function setIndex(i) {
sessionStorage.setItem(IDX_KEY, String(i));
}
function getSelectedUL() {
return sessionStorage.getItem(UL_KEY);
}
function setSelectedUL(selector) {
sessionStorage.setItem(UL_KEY, selector);
}
function enableULSelection() {
// Create visual indicators
const style = document.createElement("style");
style.id = "namer-ul-selection-style";
style.textContent = `
.namer-ul-highlight {
outline: 3px solid #9932cc !important;
outline-offset: 2px !important;
cursor: pointer !important;
position: relative !important;
}
.namer-ul-highlight::before {
content: "Click to select this list for naming";
position: absolute;
top: -25px;
left: 0;
background: #9932cc;
color: white;
padding: 2px 8px;
font-size: 12px;
border-radius: 3px;
white-space: nowrap;
z-index: 10000;
}
.namer-selection-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0,0,0,0.3);
z-index: 9999;
pointer-events: none;
}
.namer-instruction {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: #9932cc;
color: white;
padding: 20px;
border-radius: 8px;
font-family: monospace;
font-size: 16px;
z-index: 10001;
text-align: center;
box-shadow: 0 4px 20px rgba(0,0,0,0.5);
}
`;
document.head.appendChild(style);
// Create overlay
const overlay = document.createElement("div");
overlay.className = "namer-selection-overlay";
overlay.id = "namer-overlay";
document.body.appendChild(overlay);
// Create instruction
const instruction = document.createElement("div");
instruction.className = "namer-instruction";
instruction.id = "namer-instruction";
instruction.innerHTML = `
<div style="margin-bottom: 10px;"><strong>📝 Namer Setup</strong></div>
<div>Hover over UL elements and click the one containing the pets you want to name</div>
<div style="margin-top: 10px; font-size: 12px;">Press ESC to cancel</div>
`;
document.body.appendChild(instruction);
// Find all UL elements and add hover effects
const uls = document.querySelectorAll("ul");
uls.forEach((ul) => {
ul.addEventListener("mouseenter", handleULHover);
ul.addEventListener("mouseleave", handleULLeave);
ul.addEventListener("click", handleULClick);
});
// ESC to cancel
document.addEventListener("keydown", handleEscapeKey);
log(
`Found ${uls.length} UL elements - hover to highlight, click to select`
);
}
function disableULSelection() {
// Remove style
const style = document.getElementById("namer-ul-selection-style");
if (style) style.remove();
// Remove overlay and instruction
const overlay = document.getElementById("namer-overlay");
if (overlay) overlay.remove();
const instruction = document.getElementById("namer-instruction");
if (instruction) instruction.remove();
// Remove event listeners from all ULs
const uls = document.querySelectorAll("ul");
uls.forEach((ul) => {
ul.removeEventListener("mouseenter", handleULHover);
ul.removeEventListener("mouseleave", handleULLeave);
ul.removeEventListener("click", handleULClick);
ul.classList.remove("namer-ul-highlight");
});
document.removeEventListener("keydown", handleEscapeKey);
}
function handleULHover(event) {
const ul = event.target;
ul.classList.add("namer-ul-highlight");
// Add debug info overlay
const debugInfo = document.createElement("div");
debugInfo.id = "namer-debug-info";
debugInfo.style.cssText = `
position: fixed;
top: 10px;
right: 10px;
background: #9932cc;
color: white;
padding: 10px;
border-radius: 5px;
font-family: monospace;
font-size: 12px;
z-index: 10002;
max-width: 300px;
`;
const petLinks = ul.querySelectorAll('a.pet[href*="pet="]').length;
const totalLis = ul.querySelectorAll("li").length;
debugInfo.innerHTML = `
<strong>🔍 UL Preview</strong><br>
Total LIs: ${totalLis}<br>
Pet Links: ${petLinks}<br>
${
petLinks > 0
? "✅ Contains pet links!"
: "❌ No pet links found"
}
`;
document.body.appendChild(debugInfo);
}
function handleULLeave(event) {
event.target.classList.remove("namer-ul-highlight");
// Remove debug info
const debugInfo = document.getElementById("namer-debug-info");
if (debugInfo) debugInfo.remove();
}
function handleULClick(event) {
event.preventDefault();
event.stopPropagation();
const ul = event.target;
// Remove highlight class
ul.classList.remove("namer-ul-highlight");
log(`Selected UL with ${ul.children.length} children`);
// Clean up selection mode first
disableULSelection();
// ask what the name should be with an alert prompt
const name = prompt("Enter the name to use for all pets:", "cute guy");
if (name) {
sessionStorage.setItem(NAME, name);
log(`Using name: ${name}`);
} else {
log('No name provided, using default "cute guy"');
sessionStorage.setItem(NAME, "cute guy");
}
// Collect pets immediately from the selected UL
collectPetsFromElement(ul);
}
function collectPetsFromElement(ul) {
if (!isRunning()) return;
log("Collecting pet links from selected UL");
if (!ul) {
log("No UL element provided");
stopBot();
return;
}
log(`Found UL element with ${ul.children.length} child elements`);
// Extract pet links specifically
const petLinks = [];
const lis = ul.querySelectorAll("li");
log(`Scanning ${lis.length} li elements for pet links...`);
lis.forEach((li, index) => {
// Look for pet links - using the pattern from your example
const petLink =
li.querySelector('a.pet[href*="pet="]') ||
li.querySelector('a[href*="pet="]') ||
li.querySelector('a[href*="sub=profile"]');
if (petLink && petLink.href) {
const href = petLink.href;
// Check if it's a pet profile link (not a user link)
const isPetLink =
href.includes("pet=") && href.includes("sub=profile");
if (isPetLink) {
petLinks.push(href);
log(`Found pet link ${index + 1}: ${href}`);
}
}
});
if (!petLinks.length) {
log("No pet links found in selected UL");
log("UL HTML preview:", ul.innerHTML.substring(0, 500));
// Debug what we actually found
log("Debug: Found links in UL:");
const allLinks = ul.querySelectorAll("a");
allLinks.forEach((link, i) => {
log(` Link ${i + 1}: href="${link.href}" class="${link.className}"`);
});
stopBot();
return;
}
log(`Found ${petLinks.length} pets to name`);
savePets(petLinks);
goToPet();
}
function handleEscapeKey(event) {
if (event.key === "Escape") {
log("Selection cancelled");
stopBot();
}
}
function goToPet() {
if (!isRunning()) return;
const pets = getPets();
const idx = getIndex();
if (idx >= pets.length) {
log("All pets named! Naming complete!");
stopBot();
location.href = getBase();
return;
}
log(`📝 Going to name pet ${idx + 1}/${pets.length}...`);
location.href = pets[idx];
}
function handlePetProfile() {
if (!isRunning()) return;
// while a close button is present, click it
// <span class="ui-button-icon ui-icon ui-icon-closethick"></span>
while (document.querySelector(".ui-button-icon.ui-icon-closethick")) {
const closeBtn = document.querySelector(
".ui-button-icon.ui-icon-closethick"
);
log("🔄 Found close button, clicking to close any open modals...");
closeBtn.click();
}
log("🔍 Looking for naming button on pet profile...");
// Single attempt with minimal wait - no retries
setTimeout(() => {
const namingBtn = document.querySelector(
"#profile > div > form > fieldset.ui-fieldset.ui-widget-content.ui-corner-all.actions > div > div > button"
);
if (namingBtn) {
log("✅ Found naming button, clicking...");
namingBtn.click();
// Wait for modal to appear with fixed delay
setTimeout(() => {
if (isRunning()) {
handleNamingModal();
}
}, 250);
} else {
log("❌ No naming button found, moving to next pet");
setIndex(getIndex() + 1);
setTimeout(() => {
if (isRunning()) {
goToPet();
}
}, 50);
}
}, 100);
}
function handleNamingModal() {
if (!isRunning()) return;
log("🔍 Looking for naming modal...");
// Single attempt - modal should be ready by now
const nameInput = document.querySelector(
"#dialog > fieldset > div > div > input[type=text]"
);
if (nameInput) {
log("✅ Found name input field, entering name...");
// Clear existing text and type new name
nameInput.value = "";
nameInput.focus();
nameInput.value = sessionStorage.getItem(NAME) || "cute guy";
// Trigger input events to ensure the change is registered
nameInput.dispatchEvent(new Event("input", { bubbles: true }));
nameInput.dispatchEvent(new Event("change", { bubbles: true }));
// click this body > div.ui-dialog.ui-corner-all.ui-widget.ui-widget-content.ui-front.ui-dialog-buttons.ui-draggable.ui-resizable > div.ui-dialog-buttonpane.ui-widget-content.ui-helper-clearfix
// just to be sure lol
document
.querySelector(
"body > div.ui-dialog.ui-corner-all.ui-widget.ui-widget-content.ui-front.ui-dialog-buttons.ui-draggable.ui-resizable > div.ui-dialog-buttonpane.ui-widget-content.ui-helper-clearfix"
)
.click();
log("📝 Name entered, looking for confirm button...");
// Minimal wait then look for confirm button
setTimeout(() => {
const confirmBtn = document.querySelector(
"body > div.ui-dialog.ui-corner-all.ui-widget.ui-widget-content.ui-front.ui-dialog-buttons.ui-draggable.ui-resizable > div.ui-dialog-buttonpane.ui-widget-content.ui-helper-clearfix > div > button:nth-child(1)"
);
if (confirmBtn) {
log("✅ Found confirm button, clicking...");
confirmBtn.click();
log("🎉 Pet named successfully! Moving to next pet...");
// Move to next pet with minimal delay
setTimeout(() => {
if (isRunning()) {
setIndex(getIndex() + 1);
goToPet();
}
}, 25);
} else {
log("❌ No confirm button found, moving to next pet");
setIndex(getIndex() + 1);
setTimeout(() => {
if (isRunning()) {
goToPet();
}
}, 50);
}
}, 100);
} else {
log("❌ No modal input found, moving to next pet");
setIndex(getIndex() + 1);
setTimeout(() => {
if (isRunning()) {
goToPet();
}
}, 50);
}
}
function main() {
if (!isRunning()) return;
const href = location.href;
const base = getBase();
log("🚀 Namer main executing...", href);
// If we're on a pet profile page
if (href.includes("sub=profile") && href.includes("pet=")) {
log("📍 On pet profile, looking for naming button");
handlePetProfile();
}
// If we're back at the base page and haven't collected pets yet
else if (href === base && !sessionStorage.getItem(PETS_KEY)) {
log("📍 At base, need to collect pets");
setTimeout(() => {
if (isRunning()) {
// Re-enable UL selection
enableULSelection();
}
}, 50);
}
// If we're back at the base page and have pets collected
else if (href === base) {
log("📍 At base, have pets, continuing...");
setTimeout(() => {
if (isRunning()) {
goToPet();
}
}, 50);
}
// If we're on a different page
else {
const pets = getPets();
if (pets.length > 0) {
log("📍 On different page, going to next pet");
setTimeout(() => {
if (isRunning()) {
goToPet();
}
}, 50);
} else {
log("📍 No pets collected, returning to base");
location.href = base;
}
}
}
return { startBot, stopBot, main };
})();
// --- Image Collector Module ---
const ImageCollector = (function () {
const RUN_KEY = "image_collector_running";
const SPECIES_KEY = "image_collector_species";
const COUNT_KEY = "image_collector_count";
// List of species that the puzzle solver can recognize
const SPECIES_OPTIONS = [
"Canis",
"Draconis",
"Equus",
"Feline",
"Gekko",
"Lupus",
"Mantis",
"Raptor",
"Vulpes",
];
function log(...args) {
console.log(
"%c[IMAGE-COLLECTOR]",
"background:#ff4500;color:#fff;",
...args
);
}
function startBot() {
log("Starting Image Collector");
// Show species selection modal
showSpeciesSelection();
}
function stopBot() {
sessionStorage.removeItem(RUN_KEY);
sessionStorage.removeItem(SPECIES_KEY);
sessionStorage.removeItem(COUNT_KEY);
log("Stopped Image Collector");
}
function isRunning() {
return sessionStorage.getItem(RUN_KEY) === "true";
}
function getSelectedSpecies() {
return sessionStorage.getItem(SPECIES_KEY);
}
function setSelectedSpecies(species) {
sessionStorage.setItem(SPECIES_KEY, species);
}
function getImageCount() {
return parseInt(sessionStorage.getItem(COUNT_KEY) || "0", 10);
}
function setImageCount(count) {
sessionStorage.setItem(COUNT_KEY, String(count));
}
function incrementImageCount() {
const newCount = getImageCount() + 1;
setImageCount(newCount);
return newCount;
}
function showSpeciesSelection() {
// Create modal overlay
const overlay = document.createElement("div");
overlay.id = "image-collector-species-overlay";
overlay.style.cssText = `
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0,0,0,0.8);
z-index: 10000;
display: flex;
align-items: center;
justify-content: center;
`;
// Create modal content
const modal = document.createElement("div");
modal.style.cssText = `
background: #ff4500;
color: white;
padding: 30px;
border-radius: 10px;
font-family: monospace;
max-width: 500px;
max-height: 80vh;
overflow-y: auto;
box-shadow: 0 4px 20px rgba(0,0,0,0.5);
`;
modal.innerHTML = `
<div style="margin-bottom: 20px; text-align: center;">
<h2 style="margin: 0 0 10px 0;">📸 Image Collector</h2>
<p style="margin: 0; font-size: 14px;">Select the species you want to collect images for:</p>
</div>
<div id="species-list" style="margin-bottom: 20px; max-height: 300px; overflow-y: auto;">
${SPECIES_OPTIONS.map(
(species) => `
<div style="margin: 5px 0;">
<label style="display: flex; align-items: center; cursor: pointer; padding: 8px; border-radius: 5px; transition: background-color 0.2s;"
onmouseover="this.style.backgroundColor='rgba(255,255,255,0.1)'"
onmouseout="this.style.backgroundColor='transparent'">
<input type="radio" name="species" value="${species}" style="margin-right: 10px;">
<span>${species}</span>
</label>
</div>
`
).join("")}
</div>
<div style="text-align: center;">
<button id="species-confirm" style="margin: 5px; padding: 10px 20px; font-size: 14px; background: white; color: #ff4500; border: none; border-radius: 5px; cursor: pointer;">Start Collecting</button>
<button id="species-cancel" style="margin: 5px; padding: 10px 20px; font-size: 14px; background: #333; color: white; border: none; border-radius: 5px; cursor: pointer;">Cancel</button>
</div>
`;
overlay.appendChild(modal);
document.body.appendChild(overlay);
// Handle confirm button
document.getElementById("species-confirm").onclick = () => {
const selectedRadio = document.querySelector(
'input[name="species"]:checked'
);
if (selectedRadio) {
const species = selectedRadio.value;
setSelectedSpecies(species);
setImageCount(0);
sessionStorage.setItem(RUN_KEY, "true");
log(`Selected species: ${species}`);
// Remove modal
overlay.remove();
// Start the collection process
startCollectionLoop();
} else {
alert("Please select a species first!");
}
};
// Handle cancel button
document.getElementById("species-cancel").onclick = () => {
overlay.remove();
log("Species selection cancelled");
};
// Handle ESC key
document.addEventListener("keydown", function escHandler(event) {
if (event.key === "Escape") {
overlay.remove();
document.removeEventListener("keydown", escHandler);
log("Species selection cancelled");
}
});
}
function startCollectionLoop() {
if (!isRunning()) return;
const species = getSelectedSpecies();
log(`🔄 Starting collection loop for ${species}...`);
// Start the main collection process
setTimeout(() => {
if (isRunning()) {
collectCurrentImage();
}
}, 100);
}
function collectCurrentImage() {
if (!isRunning()) return;
const species = getSelectedSpecies();
const currentCount = incrementImageCount();
const paddedCount = String(currentCount).padStart(4, "0");
const filename = `${species}_${paddedCount}`;
log(`📸 Collecting image ${currentCount} for ${species}...`);
// Find the image element
const imageElement = document.querySelector(
"#visualizer > div > form > fieldset.ui-fieldset.ui-widget-content.ui-corner-all.display > div > img"
);
if (!imageElement) {
log(
"❌ Image element not found. Make sure you are on the visualizer page."
);
stopBot();
return;
}
// Get the image URL and download it
const imageUrl = imageElement.src;
if (imageUrl) {
downloadImage(imageUrl, filename)
.then(() => {
log(`✅ Image saved as ${filename}`);
// Wait a moment then click randomize button
setTimeout(() => {
if (isRunning()) {
clickRandomizeButton();
}
}, 100);
})
.catch((error) => {
log(`❌ Failed to save image: ${error}`);
// Continue anyway
setTimeout(() => {
if (isRunning()) {
clickRandomizeButton();
}
}, 100);
});
} else {
log("❌ No image URL found");
setTimeout(() => {
if (isRunning()) {
clickRandomizeButton();
}
}, 100);
}
}
function downloadImage(url, filename) {
return new Promise((resolve, reject) => {
// Convert relative URL to absolute if needed
const imageUrl = url.startsWith("//") ? `https:${url}` : url;
GM_xmlhttpRequest({
method: "GET",
url: imageUrl,
responseType: "blob",
onload: function (response) {
try {
// Create blob URL
const blob = response.response;
const blobUrl = URL.createObjectURL(blob);
// Create download link
const downloadLink = document.createElement("a");
downloadLink.href = blobUrl;
downloadLink.download = `${filename}.jpg`;
downloadLink.style.display = "none";
// Add to document and click
document.body.appendChild(downloadLink);
downloadLink.click();
// Clean up
document.body.removeChild(downloadLink);
URL.revokeObjectURL(blobUrl);
resolve();
} catch (error) {
reject(error);
}
},
onerror: function (error) {
reject(error);
},
});
});
}
function clickRandomizeButton() {
if (!isRunning()) return;
log("🎲 Clicking randomize button...");
// Find the randomize button
const randomizeButton = document.querySelector(
"#visualizer > div > form > fieldset.ui-fieldset.ui-widget-content.ui-corner-all.actions > div > div:nth-child(2) > button"
);
if (randomizeButton) {
randomizeButton.click();
log("✅ Randomize button clicked");
// Wait for the new image to load, then collect it
setTimeout(() => {
if (isRunning()) {
collectCurrentImage();
}
}, 100); // Wait for image to update
} else {
log(
"❌ Randomize button not found. Make sure you are on the visualizer page."
);
stopBot();
}
}
function main() {
if (!isRunning()) return;
// This module doesn't need URL-based navigation
// It works on the current page where the user starts it
log("🚀 Image Collector running...");
}
return { startBot, stopBot, main };
})();
// --- Genetic Profiler Module ---
const GeneticProfiler = (function () {
const RUN_KEY = "genetic_profiler_running";
const PETS_KEY = "genetic_profiler_pets";
const IDX_KEY = "genetic_profiler_index";
const BASE_KEY = "genetic_profiler_base_href";
const UL_KEY = "genetic_profiler_selected_ul";
const COLORS_KEY = "genetic_profiler_target_colors";
function log(...args) {
console.log(
"%c[GENETIC-PROFILER]",
"background:#4b0082;color:#fff;",
...args
);
}
function startBot() {
log(
"Starting Genetic Profiler - First, please enter target color values"
);
// Show color input modal
showColorInputModal();
}
function stopBot() {
sessionStorage.removeItem(RUN_KEY);
sessionStorage.removeItem(UL_KEY);
sessionStorage.removeItem(COLORS_KEY);
disableULSelection();
log("Stopped Genetic Profiler");
}
function isRunning() {
return sessionStorage.getItem(RUN_KEY) === "true";
}
function getBase() {
return sessionStorage.getItem(BASE_KEY);
}
function getPets() {
try {
return JSON.parse(sessionStorage.getItem(PETS_KEY)) || [];
} catch {
return [];
}
}
function savePets(pets) {
sessionStorage.setItem(PETS_KEY, JSON.stringify(pets));
}
function getIndex() {
return parseInt(sessionStorage.getItem(IDX_KEY) || "0", 10);
}
function setIndex(i) {
sessionStorage.setItem(IDX_KEY, String(i));
}
function getSelectedUL() {
return sessionStorage.getItem(UL_KEY);
}
function setSelectedUL(selector) {
sessionStorage.setItem(UL_KEY, selector);
}
function getTargetColors() {
try {
return JSON.parse(sessionStorage.getItem(COLORS_KEY)) || {};
} catch {
return {};
}
}
function setTargetColors(colors) {
sessionStorage.setItem(COLORS_KEY, JSON.stringify(colors));
}
function showColorInputModal() {
// Create modal overlay
const overlay = document.createElement("div");
overlay.id = "genetic-profiler-color-overlay";
overlay.style.cssText = `
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0,0,0,0.8);
z-index: 10000;
display: flex;
align-items: center;
justify-content: center;
`;
// Create modal content
const modal = document.createElement("div");
modal.style.cssText = `
background: #4b0082;
color: white;
padding: 30px;
border-radius: 10px;
font-family: monospace;
max-width: 500px;
box-shadow: 0 4px 20px rgba(0,0,0,0.5);
`;
modal.innerHTML = `
<div style="margin-bottom: 20px; text-align: center;">
<h2 style="margin: 0 0 10px 0;">🧬 Genetic Profiler Setup</h2>
<p style="margin: 0; font-size: 14px;">Enter target hex color values (without #):</p>
</div>
<div style="margin-bottom: 20px;">
<div style="margin-bottom: 10px;">
<label style="display: block; margin-bottom: 5px;">Eyes:</label>
<input type="text" id="target-eyes" placeholder="538EA2" style="width: 100%; padding: 8px; border: none; border-radius: 4px; font-family: monospace;" maxlength="6">
</div>
<div style="margin-bottom: 10px;">
<label style="display: block; margin-bottom: 5px;">Body1:</label>
<input type="text" id="target-body1" placeholder="8C1C2C" style="width: 100%; padding: 8px; border: none; border-radius: 4px; font-family: monospace;" maxlength="6">
</div>
<div style="margin-bottom: 10px;">
<label style="display: block; margin-bottom: 5px;">Body2:</label>
<input type="text" id="target-body2" placeholder="A4D9E5" style="width: 100%; padding: 8px; border: none; border-radius: 4px; font-family: monospace;" maxlength="6">
</div>
<div style="margin-bottom: 10px;">
<label style="display: block; margin-bottom: 5px;">Extra1:</label>
<input type="text" id="target-extra1" placeholder="196F52" style="width: 100%; padding: 8px; border: none; border-radius: 4px; font-family: monospace;" maxlength="6">
</div>
<div style="margin-bottom: 10px;">
<label style="display: block; margin-bottom: 5px;">Extra2:</label>
<input type="text" id="target-extra2" placeholder="383F7E" style="width: 100%; padding: 8px; border: none; border-radius: 4px; font-family: monospace;" maxlength="6">
</div>
</div>
<div style="text-align: center;">
<button id="colors-confirm" style="margin: 5px; padding: 10px 20px; font-size: 14px; background: white; color: #4b0082; border: none; border-radius: 5px; cursor: pointer;">Next: Select Pets</button>
<button id="colors-cancel" style="margin: 5px; padding: 10px 20px; font-size: 14px; background: #333; color: white; border: none; border-radius: 5px; cursor: pointer;">Cancel</button>
</div>
`;
overlay.appendChild(modal);
document.body.appendChild(overlay);
// Handle confirm button
document.getElementById("colors-confirm").onclick = () => {
const eyes = document
.getElementById("target-eyes")
.value.trim()
.replace("#", "");
const body1 = document
.getElementById("target-body1")
.value.trim()
.replace("#", "");
const body2 = document
.getElementById("target-body2")
.value.trim()
.replace("#", "");
const extra1 = document
.getElementById("target-extra1")
.value.trim()
.replace("#", "");
const extra2 = document
.getElementById("target-extra2")
.value.trim()
.replace("#", "");
// Validate hex colors
const hexPattern = /^[0-9A-Fa-f]{6}$/;
if (
!hexPattern.test(eyes) ||
!hexPattern.test(body1) ||
!hexPattern.test(body2) ||
!hexPattern.test(extra1) ||
!hexPattern.test(extra2)
) {
alert(
"Please enter valid 6-character hex color codes (e.g., 538EA2)"
);
return;
}
const colors = {
eyes: eyes.toUpperCase(),
body1: body1.toUpperCase(),
body2: body2.toUpperCase(),
extra1: extra1.toUpperCase(),
extra2: extra2.toUpperCase(),
};
setTargetColors(colors);
log("Target colors saved:", colors);
// Store the current location as base
sessionStorage.setItem(BASE_KEY, location.href);
sessionStorage.setItem(RUN_KEY, "true");
sessionStorage.setItem(IDX_KEY, "0");
sessionStorage.removeItem(PETS_KEY);
sessionStorage.removeItem(UL_KEY);
// Remove modal
overlay.remove();
// Enable UL selection mode
enableULSelection();
};
// Handle cancel button
document.getElementById("colors-cancel").onclick = () => {
overlay.remove();
log("Color input cancelled");
};
// Handle ESC key
document.addEventListener("keydown", function escHandler(event) {
if (event.key === "Escape") {
overlay.remove();
document.removeEventListener("keydown", escHandler);
log("Color input cancelled");
}
});
}
function enableULSelection() {
// Create visual indicators
const style = document.createElement("style");
style.id = "genetic-profiler-ul-selection-style";
style.textContent = `
.genetic-profiler-ul-highlight {
outline: 3px solid #4b0082 !important;
outline-offset: 2px !important;
cursor: pointer !important;
position: relative !important;
}
.genetic-profiler-ul-highlight::before {
content: "Click to select this list for genetic profiling";
position: absolute;
top: -25px;
left: 0;
background: #4b0082;
color: white;
padding: 2px 8px;
font-size: 12px;
border-radius: 3px;
white-space: nowrap;
z-index: 10000;
}
.genetic-profiler-selection-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0,0,0,0.3);
z-index: 9999;
pointer-events: none;
}
.genetic-profiler-instruction {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: #4b0082;
color: white;
padding: 20px;
border-radius: 8px;
font-family: monospace;
font-size: 16px;
z-index: 10001;
text-align: center;
box-shadow: 0 4px 20px rgba(0,0,0,0.5);
}
`;
document.head.appendChild(style);
// Create overlay
const overlay = document.createElement("div");
overlay.className = "genetic-profiler-selection-overlay";
overlay.id = "genetic-profiler-overlay";
document.body.appendChild(overlay);
// Create instruction
const instruction = document.createElement("div");
instruction.className = "genetic-profiler-instruction";
instruction.id = "genetic-profiler-instruction";
instruction.innerHTML = `
<div style="margin-bottom: 10px;"><strong>🧬 Genetic Profiler</strong></div>
<div>Hover over UL elements and click the one containing the pets you want to profile</div>
<div style="margin-top: 10px; font-size: 12px;">Press ESC to cancel</div>
`;
document.body.appendChild(instruction);
// Find all UL elements and add hover effects
const uls = document.querySelectorAll("ul");
uls.forEach((ul) => {
ul.addEventListener("mouseenter", handleULHover);
ul.addEventListener("mouseleave", handleULLeave);
ul.addEventListener("click", handleULClick);
});
// ESC to cancel
document.addEventListener("keydown", handleEscapeKey);
log(
`Found ${uls.length} UL elements - hover to highlight, click to select`
);
}
function disableULSelection() {
// Remove style
const style = document.getElementById(
"genetic-profiler-ul-selection-style"
);
if (style) style.remove();
// Remove overlay and instruction
const overlay = document.getElementById("genetic-profiler-overlay");
if (overlay) overlay.remove();
const instruction = document.getElementById(
"genetic-profiler-instruction"
);
if (instruction) instruction.remove();
// Remove event listeners from all ULs
const uls = document.querySelectorAll("ul");
uls.forEach((ul) => {
ul.removeEventListener("mouseenter", handleULHover);
ul.removeEventListener("mouseleave", handleULLeave);
ul.removeEventListener("click", handleULClick);
ul.classList.remove("genetic-profiler-ul-highlight");
});
document.removeEventListener("keydown", handleEscapeKey);
}
function handleULHover(event) {
const ul = event.target;
ul.classList.add("genetic-profiler-ul-highlight");
// Add debug info overlay
const debugInfo = document.createElement("div");
debugInfo.id = "genetic-profiler-debug-info";
debugInfo.style.cssText = `
position: fixed;
top: 10px;
right: 10px;
background: #4b0082;
color: white;
padding: 10px;
border-radius: 5px;
font-family: monospace;
font-size: 12px;
z-index: 10002;
max-width: 300px;
`;
const petLinks = ul.querySelectorAll('a.pet[href*="pet="]').length;
const totalLis = ul.querySelectorAll("li").length;
debugInfo.innerHTML = `
<strong>🔍 UL Preview</strong><br>
Total LIs: ${totalLis}<br>
Pet Links: ${petLinks}<br>
${
petLinks > 0
? "✅ Contains pet links!"
: "❌ No pet links found"
}
`;
document.body.appendChild(debugInfo);
}
function handleULLeave(event) {
event.target.classList.remove("genetic-profiler-ul-highlight");
// Remove debug info
const debugInfo = document.getElementById("genetic-profiler-debug-info");
if (debugInfo) debugInfo.remove();
}
function handleULClick(event) {
event.preventDefault();
event.stopPropagation();
const ul = event.target;
// Remove highlight class
ul.classList.remove("genetic-profiler-ul-highlight");
log(`Selected UL with ${ul.children.length} children`);
// Clean up selection mode first
disableULSelection();
// Collect pets immediately from the selected UL
collectPetsFromElement(ul);
}
function collectPetsFromElement(ul) {
if (!isRunning()) return;
log("Collecting pet links from selected UL");
if (!ul) {
log("No UL element provided");
stopBot();
return;
}
log(`Found UL element with ${ul.children.length} child elements`);
// Extract pet links specifically
const petLinks = [];
const lis = ul.querySelectorAll("li");
log(`Scanning ${lis.length} li elements for pet links...`);
lis.forEach((li, index) => {
// Look for pet links
var petLink =
li.querySelector('a.pet[href*="pet="]') ||
li.querySelector('a[href*="pet="]') ||
li.querySelector('a[href*="sub=profile"]');
if (petLink && petLink.href) {
const href = petLink.href;
// Check if it's a pet profile link (not a user link)
const isPetLink =
href.includes("pet=") && href.includes("sub=profile");
if (isPetLink) {
petLinks.push(href);
log(`Found pet link ${index + 1}: ${href}`);
}
}
});
if (!petLinks.length) {
log("No pet links found in selected UL");
log("UL HTML preview:", ul.innerHTML.substring(0, 500));
// Debug what we actually found
log("Debug: Found links in UL:");
const allLinks = ul.querySelectorAll("a");
allLinks.forEach((link, i) => {
log(` Link ${i + 1}: href="${link.href}" class="${link.className}"`);
});
stopBot();
return;
}
log(`Found ${petLinks.length} pets to profile`);
// reverse petLinks to profile in reverse order
petLinks.reverse();
savePets(petLinks);
goToPet();
}
function handleEscapeKey(event) {
if (event.key === "Escape") {
log("Selection cancelled");
stopBot();
}
}
function goToPet() {
if (!isRunning()) return;
const pets = getPets();
const idx = getIndex();
if (idx >= pets.length) {
log("All pets profiled! Genetic profiling complete!");
stopBot();
location.href = getBase();
return;
}
log(`🧬 Going to profile pet ${idx + 1}/${pets.length}...`);
location.href = pets[idx];
}
function hexToRgb(hex) {
const r = parseInt(hex.substring(0, 2), 16);
const g = parseInt(hex.substring(2, 4), 16);
const b = parseInt(hex.substring(4, 6), 16);
return { r, g, b };
}
function parseColorFromElement(element) {
if (!element) return null;
// Look for hex color in the text content
const text = element.textContent || "";
const hexMatch = text.match(/#([0-9A-Fa-f]{6})/);
if (hexMatch) {
return hexMatch[1].toUpperCase();
}
return null;
}
function calculateColorDifferences(targetColors, actualColors) {
const differences = {};
for (const [colorName, targetHex] of Object.entries(targetColors)) {
const actualHex = actualColors[colorName];
if (!actualHex) {
differences[colorName] = "N/A";
continue;
}
const targetRgb = hexToRgb(targetHex);
const actualRgb = hexToRgb(actualHex);
const rDiff = actualRgb.r - targetRgb.r;
const gDiff = actualRgb.g - targetRgb.g;
const bDiff = actualRgb.b - targetRgb.b;
const formatDiff = (diff) => {
if (diff > 0) return `+${diff}`;
if (diff < 0) return `${diff}`;
return "0";
};
differences[colorName] = `${formatDiff(rDiff)}, ${formatDiff(
gDiff
)}, ${formatDiff(bDiff)}`;
}
return differences;
}
function handlePetProfile() {
if (!isRunning()) return;
log("🔍 Analyzing pet colors...");
setTimeout(() => {
if (!isRunning()) return;
// Find the colors fieldset
const colorsFieldset = document.querySelector(
"#profile > div > form > fieldset.ui-fieldset.ui-widget-content.ui-corner-all.colors"
);
if (!colorsFieldset) {
log("❌ Colors fieldset not found, skipping this pet");
setIndex(getIndex() + 1);
setTimeout(() => {
if (isRunning()) {
goToPet();
}
}, 300);
return;
}
// Extract actual colors from the pet
const actualColors = {};
// Eyes color
const eyesRow = colorsFieldset.querySelector("#Eyes");
if (eyesRow) {
const eyesColor = parseColorFromElement(
eyesRow.querySelector("td.c1 var")
);
if (eyesColor) actualColors.eyes = eyesColor;
}
// Body colors
const bodyRow = colorsFieldset.querySelector("#Body");
if (bodyRow) {
const body1Color = parseColorFromElement(
bodyRow.querySelector("td.c1 var")
);
const body2Color = parseColorFromElement(
bodyRow.querySelector("td.c2 var")
);
if (body1Color) actualColors.body1 = body1Color;
if (body2Color) actualColors.body2 = body2Color;
}
// Extra colors
const extraRow = colorsFieldset.querySelector("#Extra");
if (extraRow) {
const extra1Color = parseColorFromElement(
extraRow.querySelector("td.c1 var")
);
const extra2Color = parseColorFromElement(
extraRow.querySelector("td.c2 var")
);
if (extra1Color) actualColors.extra1 = extra1Color;
if (extra2Color) actualColors.extra2 = extra2Color;
}
log("Actual colors found:", actualColors);
// Calculate differences
const targetColors = getTargetColors();
const differences = calculateColorDifferences(
targetColors,
actualColors
);
log("Color differences:", differences);
var sum_of_absolute_value_of_differences_per_color = [];
// we want to make a sum row that shows the sum of the absolute value of the differences per color
// example data:
// Eyes: 1, -2, 3
// Body1: -4, 5, 6
// Body2: 7, -8, 9
// Extra1: 0, 0, 0
// Extra2: 0, 0, 0
// example sum row of example data: Sum: 12, 15, 18
// explanation of sum row:
// 12 = 1 + |-4| + 7
// 15 = |-2| + 5 + |-8|
// 18 = 3 + 6 + 9
// example is sum of the rgb columns of the below summaryLines
for (const color in differences) {
const diff = differences[color];
if (diff === "N/A") {
sum_of_absolute_value_of_differences_per_color.push("N/A");
continue;
}
const rgbValues = diff
.split(",")
.map((v) => Math.abs(parseInt(v.trim(), 10)));
if (sum_of_absolute_value_of_differences_per_color.length === 0) {
sum_of_absolute_value_of_differences_per_color = rgbValues;
} else {
sum_of_absolute_value_of_differences_per_color =
sum_of_absolute_value_of_differences_per_color.map(
(sum, index) => {
return sum === "N/A" ? "N/A" : sum + rgbValues[index];
}
);
}
}
log(
"Sum of absolute value of differences per color:",
sum_of_absolute_value_of_differences_per_color
);
// Add sum row to differences
differences.sum = sum_of_absolute_value_of_differences_per_color
.map((v) => (v === "N/A" ? "N/A" : v.toString()))
.join(", ");
// Create the summary text
const summaryLines = [
`Eyes: ${differences.eyes || "N/A"}`,
`Body1: ${differences.body1 || "N/A"}`,
`Body2: ${differences.body2 || "N/A"}`,
`Extra1: ${differences.extra1 || "N/A"}`,
`Extra2: ${differences.extra2 || "N/A"}`,
"",
`Sum: ${differences.sum || "N/A"}`,
];
const summaryText = summaryLines.join("\n");
log("Generated summary:", summaryText);
// Now write this to the pet's description
writeToDescription(summaryText);
}, 500); // Wait for page to load
}
function writeToDescription(text) {
if (!isRunning()) return;
log("🖊️ Writing genetic profile to description...");
// Try to find the description div (either regular or empty)
let descDiv = document.querySelector(
"#overview > div > form > fieldset.ui-fieldset.ui-widget-content.ui-corner-all.presentation.edit_txt > div > div.parsed_txt"
);
if (!descDiv) {
descDiv = document.querySelector(
"#overview > div > form > fieldset.ui-fieldset.ui-widget-content.ui-corner-all.presentation.edit_txt > div > div.parsed_txt.empty"
);
}
if (!descDiv) {
log("❌ Description div not found, skipping this pet");
setIndex(getIndex() + 1);
setTimeout(() => {
if (isRunning()) {
goToPet();
}
}, 300);
return;
}
log("✅ Found description div, clicking to enable editing...");
descDiv.click();
// Wait for textarea to appear
setTimeout(() => {
if (!isRunning()) return;
const textarea = document.querySelector(
"#overview > div > form > fieldset.ui-fieldset.ui-widget-content.ui-corner-all.presentation.edit_txt > div > div.ui-input.are > textarea"
);
if (!textarea) {
log("❌ Textarea not found after clicking description div");
setIndex(getIndex() + 1);
setTimeout(() => {
if (isRunning()) {
goToPet();
}
}, 300);
return;
}
log("✅ Found textarea, writing genetic profile...");
// Get existing text and append our analysis
const existingText = textarea.value || "";
const newText = existingText
? `${existingText}\n\n--- Genetic Profile ---\n${text}`
: `--- Genetic Profile ---\n${text}`;
// Clear and write new text
textarea.value = "";
textarea.focus();
textarea.value = newText;
// Trigger events to ensure change is registered
textarea.dispatchEvent(new Event("input", { bubbles: true }));
textarea.dispatchEvent(new Event("change", { bubbles: true }));
log("📝 Genetic profile written to description");
// Wait a moment then save the description
setTimeout(() => {
if (isRunning()) {
saveDescription();
}
}, 300);
}, 500);
}
function saveDescription() {
if (!isRunning()) return;
log("💾 Saving description...");
// Find the save UL
const saveUL = document.querySelector(
"#overview > div > form > fieldset.ui-fieldset.ui-widget-content.ui-corner-all.overview > div > ul"
);
if (!saveUL) {
log("❌ Save UL not found");
setIndex(getIndex() + 1);
setTimeout(() => {
if (isRunning()) {
goToPet();
}
}, 300);
return;
}
log("✅ Found save UL, clicking to save...");
saveUL.click();
log("🎉 Pet genetic profile saved! Moving to next pet...");
// Move to next pet
setTimeout(() => {
if (isRunning()) {
setIndex(getIndex() + 1);
goToPet();
}
}, 500);
}
function main() {
if (!isRunning()) return;
const href = location.href;
const base = getBase();
log("🚀 Genetic Profiler main executing...", href);
// If we're on a pet profile page
if (href.includes("sub=profile") && href.includes("pet=")) {
log("📍 On pet profile, analyzing genetic data");
setTimeout(() => {
if (isRunning()) {
handlePetProfile();
}
}, 300);
}
// If we're back at the base page and haven't collected pets yet
else if (href === base && !sessionStorage.getItem(PETS_KEY)) {
log("📍 At base, need to collect pets");
setTimeout(() => {
if (isRunning()) {
// Re-enable UL selection
enableULSelection();
}
}, 300);
}
// If we're back at the base page and have pets collected
else if (href === base) {
log("📍 At base, have pets, continuing...");
setTimeout(() => {
if (isRunning()) {
goToPet();
}
}, 300);
}
// If we're on a different page
else {
const pets = getPets();
if (pets.length > 0) {
log("📍 On different page, going to next pet");
setTimeout(() => {
if (isRunning()) {
goToPet();
}
}, 300);
} else {
log("📍 No pets collected, returning to base");
location.href = base;
}
}
}
return { startBot, stopBot, main };
})();
// --- Feeder Module ---
const Feeder = (function () {
const RUN_KEY = "feeder_running";
const PETS_KEY = "feeder_pets";
const IDX_KEY = "feeder_index";
const BASE_KEY = "feeder_base_href";
const UL_KEY = "feeder_selected_ul";
function log(...args) {
console.log("%c[FEEDER]", "background:#32cd32;color:#fff;", ...args);
}
function startBot() {
log(
"Starting Feeder - Please click on the UL element containing the pets to feed"
);
// Store the current location as base
sessionStorage.setItem(BASE_KEY, location.href);
sessionStorage.setItem(RUN_KEY, "true");
sessionStorage.setItem(IDX_KEY, "0");
sessionStorage.removeItem(PETS_KEY);
sessionStorage.removeItem(UL_KEY);
// Enable UL selection mode
enableULSelection();
}
function stopBot() {
sessionStorage.removeItem(RUN_KEY);
sessionStorage.removeItem(UL_KEY);
disableULSelection();
log("Stopped Feeder");
}
function isRunning() {
return sessionStorage.getItem(RUN_KEY) === "true";
}
function getBase() {
return sessionStorage.getItem(BASE_KEY);
}
function getPets() {
try {
return JSON.parse(sessionStorage.getItem(PETS_KEY)) || [];
} catch {
return [];
}
}
function savePets(pets) {
sessionStorage.setItem(PETS_KEY, JSON.stringify(pets));
}
function getIndex() {
return parseInt(sessionStorage.getItem(IDX_KEY) || "0", 10);
}
function setIndex(i) {
sessionStorage.setItem(IDX_KEY, String(i));
}
function getSelectedUL() {
return sessionStorage.getItem(UL_KEY);
}
function setSelectedUL(selector) {
sessionStorage.setItem(UL_KEY, selector);
}
function enableULSelection() {
// Create visual indicators
const style = document.createElement("style");
style.id = "feeder-ul-selection-style";
style.textContent = `
.feeder-ul-highlight {
outline: 3px solid #32cd32 !important;
outline-offset: 2px !important;
cursor: pointer !important;
position: relative !important;
}
.feeder-ul-highlight::before {
content: "Click to select this list for feeding";
position: absolute;
top: -25px;
left: 0;
background: #32cd32;
color: white;
padding: 2px 8px;
font-size: 12px;
border-radius: 3px;
white-space: nowrap;
z-index: 10000;
}
.feeder-selection-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0,0,0,0.3);
z-index: 9999;
pointer-events: none;
}
.feeder-instruction {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: #32cd32;
color: white;
padding: 20px;
border-radius: 8px;
font-family: monospace;
font-size: 16px;
z-index: 10001;
text-align: center;
box-shadow: 0 4px 20px rgba(0,0,0,0.5);
}
`;
document.head.appendChild(style);
// Create overlay
const overlay = document.createElement("div");
overlay.className = "feeder-selection-overlay";
overlay.id = "feeder-overlay";
document.body.appendChild(overlay);
// Create instruction
const instruction = document.createElement("div");
instruction.className = "feeder-instruction";
instruction.id = "feeder-instruction";
instruction.innerHTML = `
<div style="margin-bottom: 10px;"><strong>🍖 Feeder Setup</strong></div>
<div>Hover over UL elements and click the one containing the pets you want to feed</div>
<div style="margin-top: 10px; font-size: 12px;">Press ESC to cancel</div>
`;
document.body.appendChild(instruction);
// Find all UL elements and add hover effects
const uls = document.querySelectorAll("ul");
uls.forEach((ul) => {
ul.addEventListener("mouseenter", handleULHover);
ul.addEventListener("mouseleave", handleULLeave);
ul.addEventListener("click", handleULClick);
});
// ESC to cancel
document.addEventListener("keydown", handleEscapeKey);
log(
`Found ${uls.length} UL elements - hover to highlight, click to select`
);
}
function disableULSelection() {
// Remove style
const style = document.getElementById("feeder-ul-selection-style");
if (style) style.remove();
// Remove overlay and instruction
const overlay = document.getElementById("feeder-overlay");
if (overlay) overlay.remove();
const instruction = document.getElementById("feeder-instruction");
if (instruction) instruction.remove();
// Remove event listeners from all ULs
const uls = document.querySelectorAll("ul");
uls.forEach((ul) => {
ul.removeEventListener("mouseenter", handleULHover);
ul.removeEventListener("mouseleave", handleULLeave);
ul.removeEventListener("click", handleULClick);
ul.classList.remove("feeder-ul-highlight");
});
document.removeEventListener("keydown", handleEscapeKey);
}
function handleULHover(event) {
const ul = event.target;
ul.classList.add("feeder-ul-highlight");
// Add debug info overlay
const debugInfo = document.createElement("div");
debugInfo.id = "feeder-debug-info";
debugInfo.style.cssText = `
position: fixed;
top: 10px;
right: 10px;
background: #32cd32;
color: white;
padding: 10px;
border-radius: 5px;
font-family: monospace;
font-size: 12px;
z-index: 10002;
max-width: 300px;
`;
const petLinks = ul.querySelectorAll('a.pet[href*="pet="]').length;
const totalLis = ul.querySelectorAll("li").length;
debugInfo.innerHTML = `
<strong>🔍 UL Preview</strong><br>
Total LIs: ${totalLis}<br>
Pet Links: ${petLinks}<br>
${
petLinks > 0
? "✅ Contains pet links!"
: "❌ No pet links found"
}
`;
document.body.appendChild(debugInfo);
}
function handleULLeave(event) {
event.target.classList.remove("feeder-ul-highlight");
// Remove debug info
const debugInfo = document.getElementById("feeder-debug-info");
if (debugInfo) debugInfo.remove();
}
function handleULClick(event) {
event.preventDefault();
event.stopPropagation();
const ul = event.target;
// Remove highlight class
ul.classList.remove("feeder-ul-highlight");
log(`Selected UL with ${ul.children.length} children`);
// Clean up selection mode first
disableULSelection();
// Collect pets immediately from the selected UL
collectPetsFromElement(ul);
}
function collectPetsFromElement(ul) {
if (!isRunning()) return;
log("Collecting pet links from selected UL");
if (!ul) {
log("No UL element provided");
stopBot();
return;
}
log(`Found UL element with ${ul.children.length} child elements`);
// Extract pet links specifically
const petLinks = [];
const lis = ul.querySelectorAll("li");
log(`Scanning ${lis.length} li elements for pet links...`);
lis.forEach((li, index) => {
// Look for pet links
const petLink =
li.querySelector('a.pet[href*="pet="]') ||
li.querySelector('a[href*="pet="]') ||
li.querySelector('a[href*="sub=profile"]');
if (petLink && petLink.href) {
const href = petLink.href;
// Check if it's a pet profile link (not a user link)
const isPetLink =
href.includes("pet=") && href.includes("sub=profile");
if (isPetLink) {
petLinks.push(href);
log(`Found pet link ${index + 1}: ${href}`);
}
}
});
if (!petLinks.length) {
log("No pet links found in selected UL");
log("UL HTML preview:", ul.innerHTML.substring(0, 500));
// Debug what we actually found
log("Debug: Found links in UL:");
const allLinks = ul.querySelectorAll("a");
allLinks.forEach((link, i) => {
log(` Link ${i + 1}: href="${link.href}" class="${link.className}"`);
});
stopBot();
return;
}
log(`Found ${petLinks.length} pets to feed`);
savePets(petLinks);
goToPet();
}
function handleEscapeKey(event) {
if (event.key === "Escape") {
log("Selection cancelled");
stopBot();
}
}
function goToPet() {
if (!isRunning()) return;
const pets = getPets();
const idx = getIndex();
if (idx >= pets.length) {
log("All pets fed! Feeding complete!");
stopBot();
location.href = getBase();
return;
}
log(`🍖 Going to feed pet ${idx + 1}/${pets.length}...`);
location.href = pets[idx];
}
function handlePetProfile() {
if (!isRunning()) return;
log("🔍 Looking for feeding button on pet profile...");
let tries = 0;
const maxTries = 5;
const tryFeedButton = () => {
// Look for feeding button with the specific onclick pattern
const feedBtn = document.querySelector('button[onclick*="pet_feed"]');
if (feedBtn) {
log("✅ Found feeding button, clicking...");
feedBtn.click();
log("🍖 Pet fed successfully! Moving to next pet...");
// Move to next pet with minimal delay
setTimeout(() => {
if (isRunning()) {
setIndex(getIndex() + 1);
goToPet();
}
}, 250);
} else if (tries++ < maxTries) {
log(
`⏳ Feeding button not found, retrying... (${tries}/${maxTries})`
);
setTimeout(tryFeedButton, 50);
} else {
log("❌ No feeding button found, moving to next pet");
setIndex(getIndex() + 1);
setTimeout(() => {
if (isRunning()) {
goToPet();
}
}, 50);
}
};
// Start looking for button with minimal delay
setTimeout(tryFeedButton, 100);
}
function main() {
if (!isRunning()) return;
const href = location.href;
const base = getBase();
log("🚀 Feeder main executing...", href);
// If we're on a pet profile page
if (href.includes("sub=profile") && href.includes("pet=")) {
log("📍 On pet profile, looking for feeding button");
setTimeout(() => {
if (isRunning()) {
handlePetProfile();
}
}, 50);
}
// If we're back at the base page and haven't collected pets yet
else if (href === base && !sessionStorage.getItem(PETS_KEY)) {
log("📍 At base, need to collect pets");
setTimeout(() => {
if (isRunning()) {
// Re-enable UL selection
enableULSelection();
}
}, 50);
}
// If we're back at the base page and have pets collected
else if (href === base) {
log("📍 At base, have pets, continuing...");
setTimeout(() => {
if (isRunning()) {
goToPet();
}
}, 50);
}
// If we're on a different page
else {
const pets = getPets();
if (pets.length > 0) {
log("📍 On different page, going to next pet");
setTimeout(() => {
if (isRunning()) {
goToPet();
}
}, 50);
} else {
log("📍 No pets collected, returning to base");
location.href = base;
}
}
}
return { startBot, stopBot, main };
})();
// ...existing code...
// --- Hatcher Module ---
const Hatcher = (function () {
const RUN_KEY = "hatcher_running";
const PETS_KEY = "hatcher_pets";
const IDX_KEY = "hatcher_index";
const BASE_KEY = "hatcher_base_href";
const UL_KEY = "hatcher_selected_ul";
function log(...args) {
console.log("%c[HATCHER]", "background:#ffa500;color:#fff;", ...args);
}
async function waitForPageLoad() {
// Initial short wait for DOM
await new Promise((r) => setTimeout(r, 100));
// Wait until loading div disappears
while (document.querySelector("div.loading")) {
log("⏳ Waiting for page to load...");
await new Promise((r) => setTimeout(r, 100));
}
}
function startBot() {
log(
"Starting Hatcher - Please click on the UL element containing the pets to hatch"
);
// Store the current location as base
sessionStorage.setItem(BASE_KEY, location.href);
sessionStorage.setItem(RUN_KEY, "true");
sessionStorage.setItem(IDX_KEY, "0");
sessionStorage.removeItem(PETS_KEY);
sessionStorage.removeItem(UL_KEY);
// Enable UL selection mode
enableULSelection();
}
function stopBot() {
sessionStorage.removeItem(RUN_KEY);
sessionStorage.removeItem(UL_KEY);
disableULSelection();
log("Stopped Hatcher");
}
function isRunning() {
return sessionStorage.getItem(RUN_KEY) === "true";
}
function getBase() {
return sessionStorage.getItem(BASE_KEY);
}
function getPets() {
try {
return JSON.parse(sessionStorage.getItem(PETS_KEY)) || [];
} catch {
return [];
}
}
function savePets(pets) {
sessionStorage.setItem(PETS_KEY, JSON.stringify(pets));
}
function getIndex() {
return parseInt(sessionStorage.getItem(IDX_KEY) || "0", 10);
}
function setIndex(i) {
sessionStorage.setItem(IDX_KEY, String(i));
}
function getSelectedUL() {
return sessionStorage.getItem(UL_KEY);
}
function setSelectedUL(selector) {
sessionStorage.setItem(UL_KEY, selector);
}
function enableULSelection() {
// Create visual indicators
const style = document.createElement("style");
style.id = "hatcher-ul-selection-style";
style.textContent = `
.hatcher-ul-highlight {
outline: 3px solid #ffa500 !important;
outline-offset: 2px !important;
cursor: pointer !important;
position: relative !important;
}
.hatcher-ul-highlight::before {
content: "Click to select this list for hatching";
position: absolute;
top: -25px;
left: 0;
background: #ffa500;
color: white;
padding: 2px 8px;
font-size: 12px;
border-radius: 3px;
white-space: nowrap;
z-index: 10000;
}
.hatcher-selection-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0,0,0,0.3);
z-index: 9999;
pointer-events: none;
}
.hatcher-instruction {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: #ffa500;
color: white;
padding: 20px;
border-radius: 8px;
font-family: monospace;
font-size: 16px;
z-index: 10001;
text-align: center;
box-shadow: 0 4px 20px rgba(0,0,0,0.5);
}
`;
document.head.appendChild(style);
// Create overlay
const overlay = document.createElement("div");
overlay.className = "hatcher-selection-overlay";
overlay.id = "hatcher-overlay";
document.body.appendChild(overlay);
// Create instruction
const instruction = document.createElement("div");
instruction.className = "hatcher-instruction";
instruction.id = "hatcher-instruction";
instruction.innerHTML = `
<div style="margin-bottom: 10px;"><strong>🥚 Hatcher Setup</strong></div>
<div>Hover over UL elements and click the one containing the pets you want to hatch</div>
<div style="margin-top: 10px; font-size: 12px;">Press ESC to cancel</div>
`;
document.body.appendChild(instruction);
// Find all UL elements and add hover effects
const uls = document.querySelectorAll("ul");
uls.forEach((ul) => {
ul.addEventListener("mouseenter", handleULHover);
ul.addEventListener("mouseleave", handleULLeave);
ul.addEventListener("click", handleULClick);
});
// ESC to cancel
document.addEventListener("keydown", handleEscapeKey);
log(
`Found ${uls.length} UL elements - hover to highlight, click to select`
);
}
function disableULSelection() {
// Remove style
const style = document.getElementById("hatcher-ul-selection-style");
if (style) style.remove();
// Remove overlay and instruction
const overlay = document.getElementById("hatcher-overlay");
if (overlay) overlay.remove();
const instruction = document.getElementById("hatcher-instruction");
if (instruction) instruction.remove();
// Remove event listeners from all ULs
const uls = document.querySelectorAll("ul");
uls.forEach((ul) => {
ul.removeEventListener("mouseenter", handleULHover);
ul.removeEventListener("mouseleave", handleULLeave);
ul.removeEventListener("click", handleULClick);
ul.classList.remove("hatcher-ul-highlight");
});
document.removeEventListener("keydown", handleEscapeKey);
}
function handleULHover(event) {
const ul = event.target;
ul.classList.add("hatcher-ul-highlight");
// Add debug info overlay
const debugInfo = document.createElement("div");
debugInfo.id = "hatcher-debug-info";
debugInfo.style.cssText = `
position: fixed;
top: 10px;
right: 10px;
background: #ffa500;
color: white;
padding: 10px;
border-radius: 5px;
font-family: monospace;
font-size: 12px;
z-index: 10002;
max-width: 300px;
`;
const petLinks = ul.querySelectorAll('a.pet[href*="pet="]').length;
const totalLis = ul.querySelectorAll("li").length;
debugInfo.innerHTML = `
<strong>🔍 UL Preview</strong><br>
Total LIs: ${totalLis}<br>
Pet Links: ${petLinks}<br>
${petLinks > 0 ? "✅ Contains pet links!" : "❌ No pet links found"}
`;
document.body.appendChild(debugInfo);
}
function handleULLeave(event) {
event.target.classList.remove("hatcher-ul-highlight");
// Remove debug info
const debugInfo = document.getElementById("hatcher-debug-info");
if (debugInfo) debugInfo.remove();
}
function handleULClick(event) {
event.preventDefault();
event.stopPropagation();
const ul = event.target;
// Remove highlight class
ul.classList.remove("hatcher-ul-highlight");
log(`Selected UL with ${ul.children.length} children`);
// Clean up selection mode first
disableULSelection();
// Collect pets immediately from the selected UL
collectPetsFromElement(ul);
}
function collectPetsFromElement(ul) {
if (!isRunning()) return;
log("Collecting pet links from selected UL");
if (!ul) {
log("No UL element provided");
stopBot();
return;
}
log(`Found UL element with ${ul.children.length} child elements`);
// Extract pet links specifically
const petLinks = [];
const lis = ul.querySelectorAll("li");
log(`Scanning ${lis.length} li elements for pet links...`);
lis.forEach((li, index) => {
// Look for pet links
const petLink =
li.querySelector('a.pet[href*="pet="]') ||
li.querySelector('a[href*="pet="]') ||
li.querySelector('a[href*="sub=profile"]');
if (petLink && petLink.href) {
const href = petLink.href;
// Check if it's a pet profile link (not a user link)
const isPetLink =
href.includes("pet=") && href.includes("sub=profile");
if (isPetLink) {
petLinks.push(href);
log(`Found pet link ${index + 1}: ${href}`);
}
}
});
if (!petLinks.length) {
log("No pet links found in selected UL");
log("UL HTML preview:", ul.innerHTML.substring(0, 500));
// Debug what we actually found
log("Debug: Found links in UL:");
const allLinks = ul.querySelectorAll("a");
allLinks.forEach((link, i) => {
log(` Link ${i + 1}: href="${link.href}" class="${link.className}"`);
});
stopBot();
return;
}
log(`Found ${petLinks.length} pets to hatch`);
savePets(petLinks);
goToPet();
}
function handleEscapeKey(event) {
if (event.key === "Escape") {
log("Selection cancelled");
stopBot();
}
}
async function goToPet() {
if (!isRunning()) return;
const pets = getPets();
const idx = getIndex();
if (idx >= pets.length) {
log("All pets processed! Hatching complete!");
stopBot();
location.href = getBase();
return;
}
log(`🥚 Going to hatch pet ${idx + 1}/${pets.length}...`);
location.href = pets[idx];
}
async function handlePetProfile() {
if (!isRunning()) return;
log("🔍 Looking for hatch egg button on pet profile...");
// Wait for page to fully load
await waitForPageLoad();
let tries = 0;
const maxTries = 0;
const tryHatchButton = async () => {
if (!isRunning()) return;
// Look for button with "Hatch Egg" text using the base selector
const buttons = document.querySelectorAll(
"#profile > div > form > fieldset.ui-fieldset.ui-widget-content.ui-corner-all.actions > div > div > button"
);
let hatchBtn = null;
for (let btn of buttons) {
if (btn.textContent.trim().includes("Hatch Egg")) {
hatchBtn = btn;
break;
}
}
if (hatchBtn) {
log("✅ Found hatch egg button, clicking...");
hatchBtn.click();
log("🥚 Egg hatched successfully! Moving to next pet...");
// Wait for any page updates
await waitForPageLoad();
// Move to next pet
setIndex(getIndex() + 1);
await new Promise((r) => setTimeout(r, 100));
if (isRunning()) {
goToPet();
}
} else if (tries++ < maxTries) {
log(
`⏳ Hatch egg button not found, retrying... (${tries}/${maxTries})`
);
await new Promise((r) => setTimeout(r, 100));
return await tryHatchButton();
} else {
log("❌ No hatch egg button found, moving to next pet");
setIndex(getIndex() + 1);
await new Promise((r) => setTimeout(r, 100));
if (isRunning()) {
goToPet();
}
}
};
// Start looking for button
await tryHatchButton();
}
async function main() {
if (!isRunning()) return;
const href = location.href;
const base = getBase();
log("🚀 Hatcher main executing...", href);
// If we're on a pet profile page
if (href.includes("sub=profile") && href.includes("pet=")) {
log("📍 On pet profile, looking for hatch egg button");
await handlePetProfile();
}
// If we're back at the base page and haven't collected pets yet
else if (href === base && !sessionStorage.getItem(PETS_KEY)) {
log("📍 At base, need to collect pets");
await new Promise((r) => setTimeout(r, 100));
if (isRunning()) {
// Re-enable UL selection
enableULSelection();
}
}
// If we're back at the base page and have pets collected
else if (href === base) {
log("📍 At base, have pets, continuing...");
await new Promise((r) => setTimeout(r, 100));
if (isRunning()) {
goToPet();
}
}
// If we're on a different page
else {
const pets = getPets();
if (pets.length > 0) {
log("📍 On different page, going to next pet");
await new Promise((r) => setTimeout(r, 100));
if (isRunning()) {
goToPet();
}
} else {
log("📍 No pets collected, returning to base");
location.href = base;
}
}
}
return { startBot, stopBot, main };
})();
// Add this after the other modules (before createControls function)
// --- Bidder Module ---
// Replace the existing Bidder module with this updated version:
// --- Bidder Module ---
// --- Bidder Module ---
const Bidder = (function () {
const RUN_KEY = "bidder_running";
const BID_COUNT_KEY = "bidder_bid_count";
const SELECTED_BUTTON_KEY = "bidder_selected_button";
let retryTimeout = null;
function log(...args) {
console.log("%c[BIDDER]", "background:#ff6b35;color:#fff;", ...args);
}
function startBot() {
log(
"Starting Bidder - Please click on the Place Bid button you want to use"
);
sessionStorage.setItem(RUN_KEY, "true");
sessionStorage.setItem(BID_COUNT_KEY, "0");
sessionStorage.removeItem(SELECTED_BUTTON_KEY);
clearTimeout(retryTimeout);
// Enable button selection mode
enableButtonSelection();
}
function stopBot() {
sessionStorage.removeItem(RUN_KEY);
sessionStorage.removeItem(BID_COUNT_KEY);
sessionStorage.removeItem(SELECTED_BUTTON_KEY);
clearTimeout(retryTimeout);
disableButtonSelection();
log("Stopped Bidder");
}
function isRunning() {
return sessionStorage.getItem(RUN_KEY) === "true";
}
function getBidCount() {
return parseInt(sessionStorage.getItem(BID_COUNT_KEY) || "0", 10);
}
function incrementBidCount() {
const count = getBidCount() + 1;
sessionStorage.setItem(BID_COUNT_KEY, String(count));
return count;
}
function getSelectedButton() {
return sessionStorage.getItem(SELECTED_BUTTON_KEY);
}
function setSelectedButton(selector) {
sessionStorage.setItem(SELECTED_BUTTON_KEY, selector);
}
function findSelectedBidButton() {
const buttonSelector = getSelectedButton();
if (!buttonSelector) return null;
let bidButton = null;
// Handle text-based selectors
if (buttonSelector.startsWith("TEXT:")) {
const targetText = buttonSelector.substring(5); // Remove "TEXT:" prefix
const allButtons = document.querySelectorAll("button");
bidButton = Array.from(allButtons).find(
(btn) => btn.textContent.trim() === targetText
);
log(`Looking for button with text: "${targetText}"`);
} else {
// Handle regular CSS selectors
const selectedElement = document.querySelector(buttonSelector);
if (selectedElement) {
// If we selected a span inside a button, get the button
if (
selectedElement.tagName === "SPAN" &&
selectedElement.parentElement &&
selectedElement.parentElement.tagName === "BUTTON"
) {
bidButton = selectedElement.parentElement;
log("Found span inside button, using parent button");
} else if (selectedElement.tagName === "BUTTON") {
bidButton = selectedElement;
log("Found button directly");
} else {
// Look for a button within the selected element
bidButton = selectedElement.querySelector("button");
if (bidButton) {
log("Found button within selected element");
}
}
}
}
return bidButton;
}
function enableButtonSelection() {
// Create visual indicators
const style = document.createElement("style");
style.id = "bidder-button-selection-style";
style.textContent = `
.bidder-button-highlight {
outline: 3px solid #ff6b35 !important;
outline-offset: 2px !important;
cursor: pointer !important;
position: relative !important;
z-index: 10001 !important;
}
.bidder-button-highlight::before {
content: "Click to select this bid button";
position: absolute;
top: -25px;
left: 0;
background: #ff6b35;
color: white;
padding: 2px 8px;
font-size: 12px;
border-radius: 3px;
white-space: nowrap;
z-index: 10002;
pointer-events: none;
}
.bidder-selection-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0,0,0,0.3);
z-index: 9999;
pointer-events: none;
}
.bidder-instruction {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: #ff6b35;
color: white;
padding: 20px;
border-radius: 8px;
font-family: monospace;
font-size: 16px;
z-index: 10001;
text-align: center;
box-shadow: 0 4px 20px rgba(0,0,0,0.5);
}
`;
document.head.appendChild(style);
// Create overlay
const overlay = document.createElement("div");
overlay.className = "bidder-selection-overlay";
overlay.id = "bidder-overlay";
document.body.appendChild(overlay);
// Create instruction
const instruction = document.createElement("div");
instruction.className = "bidder-instruction";
instruction.id = "bidder-instruction";
instruction.innerHTML = `
<div style="margin-bottom: 10px;"><strong>🔨 Bidder Setup</strong></div>
<div>Hover over buttons and click the "Place Bid" button you want to use</div>
<div style="margin-top: 10px; font-size: 12px;">Press ESC to cancel</div>
`;
document.body.appendChild(instruction);
// Find all potential bid buttons using proper CSS selectors
const allButtons = document.querySelectorAll("button");
const potentialBidButtons = Array.from(allButtons).filter((btn) => {
const text = btn.textContent.toLowerCase();
const onclick = btn.onclick?.toString() || "";
return (
text.includes("bid") ||
text.includes("place bid") ||
onclick.includes("bid") ||
onclick.includes("auction")
);
});
log(
`Found ${potentialBidButtons.length} potential bid buttons - hover to highlight, click to select`
);
// Store original handlers for all buttons
const originalHandlers = new Map();
potentialBidButtons.forEach((button) => {
// Store original onclick handler
if (button.onclick) {
originalHandlers.set(button, button.onclick);
button.onclick = null;
}
// Add our event listeners
button.addEventListener("mouseenter", handleButtonHover);
button.addEventListener("mouseleave", handleButtonLeave);
button.addEventListener("click", function (event) {
handleButtonClick(event, originalHandlers);
});
});
// Store handlers map globally for cleanup
window.bidderOriginalHandlers = originalHandlers;
// ESC to cancel
document.addEventListener("keydown", handleEscapeKey);
}
function handleButtonHover(event) {
const button = event.target;
button.classList.add("bidder-button-highlight");
// Add debug info overlay
const debugInfo = document.createElement("div");
debugInfo.id = "bidder-debug-info";
debugInfo.style.cssText = `
position: fixed;
top: 10px;
right: 10px;
background: #ff6b35;
color: white;
padding: 10px;
border-radius: 5px;
font-family: monospace;
font-size: 12px;
z-index: 10002;
max-width: 300px;
`;
const buttonText = button.textContent.trim();
const hasOnclick = button.onclick ? "Yes" : "No";
const onclickText = button.onclick?.toString().substring(0, 50) || "None";
debugInfo.innerHTML = `
<strong>🔍 Button Preview</strong><br>
Text: "${buttonText}"<br>
Has onclick: ${hasOnclick}<br>
Onclick: ${onclickText}...<br>
${
buttonText.toLowerCase().includes("bid")
? "✅ Contains 'bid'!"
: "❓ Check if this is correct"
}
`;
document.body.appendChild(debugInfo);
}
function handleButtonLeave(event) {
event.target.classList.remove("bidder-button-highlight");
// Remove debug info
const debugInfo = document.getElementById("bidder-debug-info");
if (debugInfo) debugInfo.remove();
}
function handleButtonClick(event, originalHandlers) {
// Prevent the original button action
event.preventDefault();
event.stopPropagation();
event.stopImmediatePropagation();
const button = event.target;
// Remove highlight class
button.classList.remove("bidder-button-highlight");
log(`Selected button: "${button.textContent.trim()}"`);
// Generate a unique selector for this button
const selector = generateUniqueSelector(button);
log(`Generated selector: ${selector}`);
setSelectedButton(selector);
// Clean up selection mode IMMEDIATELY
disableButtonSelection();
// Start the bidding process after a short delay
setTimeout(() => {
if (isRunning()) {
attemptBid();
}
}, 100);
return false;
}
function disableButtonSelection() {
// Remove style
const style = document.getElementById("bidder-button-selection-style");
if (style) style.remove();
// Remove overlay and instruction
const overlay = document.getElementById("bidder-overlay");
if (overlay) overlay.remove();
const instruction = document.getElementById("bidder-instruction");
if (instruction) instruction.remove();
// Remove event listeners from all buttons and restore original handlers
const allButtons = document.querySelectorAll("button");
const originalHandlers = window.bidderOriginalHandlers;
allButtons.forEach((button) => {
button.removeEventListener("mouseenter", handleButtonHover);
button.removeEventListener("mouseleave", handleButtonLeave);
button.classList.remove("bidder-button-highlight");
// Restore original onclick handler if we stored one
if (originalHandlers && originalHandlers.has(button)) {
button.onclick = originalHandlers.get(button);
}
});
// Clean up global storage
if (window.bidderOriginalHandlers) {
delete window.bidderOriginalHandlers;
}
document.removeEventListener("keydown", handleEscapeKey);
}
function handleEscapeKey(event) {
if (event.key === "Escape") {
log("Button selection cancelled");
stopBot();
}
}
function generateUniqueSelector(element) {
// If we clicked on a span inside a button, get the button instead
let targetElement = element;
if (
element.tagName === "SPAN" &&
element.parentElement &&
element.parentElement.tagName === "BUTTON"
) {
targetElement = element.parentElement;
log("Adjusted target from span to parent button");
}
// Try ID first - but validate it's a proper CSS ID
if (targetElement.id) {
// Check if ID starts with a digit (invalid CSS selector)
if (!/^[a-zA-Z_]/.test(targetElement.id)) {
// Use attribute selector instead for numeric IDs
const escapedId = targetElement.id.replace(
/[!"#$%&'()*+,.\/:;<=>?@[\\\]^`{|}~]/g,
"\\$&"
);
const selector = `[id="${escapedId}"]`;
if (document.querySelectorAll(selector).length === 1) {
return selector;
}
} else {
return `#${targetElement.id}`;
}
}
// Try class combination
if (targetElement.className) {
const classes = targetElement.className
.split(" ")
.filter((c) => c.trim() && c !== "bidder-button-highlight");
if (classes.length > 0) {
let selector = `button.${classes.join(".")}`;
if (document.querySelectorAll(selector).length === 1) {
return selector;
}
}
}
// Try parent-child relationship
let parent = targetElement.parentElement;
if (parent) {
let parentSelector = "";
// Handle parent ID (with same validation)
if (parent.id) {
if (!/^[a-zA-Z_]/.test(parent.id)) {
const escapedId = parent.id.replace(
/[!"#$%&'()*+,.\/:;<=>?@[\\\]^`{|}~]/g,
"\\$&"
);
parentSelector = `[id="${escapedId}"]`;
} else {
parentSelector = `#${parent.id}`;
}
const selector = `${parentSelector} > button`;
if (document.querySelectorAll(selector).length === 1) {
return selector;
}
}
// Try parent class
if (parent.className) {
const classes = parent.className.split(" ").filter((c) => c.trim());
if (classes.length > 0) {
let selector = `.${classes.join(".")} > button`;
if (document.querySelectorAll(selector).length === 1) {
return selector;
}
}
}
}
// Try onclick attribute matching
if (targetElement.onclick) {
const onclickStr = targetElement.onclick.toString();
if (onclickStr.includes("auction_bid")) {
return 'button[onclick*="auction_bid"]';
}
}
// Text content-based selector (store text, not CSS selector)
const buttonText = targetElement.textContent.trim();
if (buttonText) {
const buttonsWithSameText = Array.from(
document.querySelectorAll("button")
).filter((btn) => btn.textContent.trim() === buttonText);
if (buttonsWithSameText.length === 1) {
// Return a special text-based identifier
return `TEXT:${buttonText}`;
}
}
// Position-based selector as fallback
const siblings = Array.from(targetElement.parentElement?.children || []);
const buttonSiblings = siblings.filter((el) => el.tagName === "BUTTON");
if (buttonSiblings.length > 1) {
const index = buttonSiblings.indexOf(targetElement) + 1;
if (parent) {
let parentSelector = "";
if (parent.id) {
if (!/^[a-zA-Z_]/.test(parent.id)) {
const escapedId = parent.id.replace(
/[!"#$%&'()*+,.\/:;<=>?@[\\\]^`{|}~]/g,
"\\$&"
);
parentSelector = `[id="${escapedId}"]`;
} else {
parentSelector = `#${parent.id}`;
}
return `${parentSelector} button:nth-of-type(${index})`;
}
if (parent.className) {
const classes = parent.className.split(" ").filter((c) => c.trim());
if (classes.length > 0) {
return `.${classes.join(".")} button:nth-of-type(${index})`;
}
}
}
}
// Final fallback: XPath-like approach but ensure we target the button
function getElementPath(el) {
if (el.id) {
if (!/^[a-zA-Z_]/.test(el.id)) {
const escapedId = el.id.replace(
/[!"#$%&'()*+,.\/:;<=>?@[\\\]^`{|}~]/g,
"\\$&"
);
return `[id="${escapedId}"]`;
} else {
return `#${el.id}`;
}
}
if (el === document.body) return "body";
const parent = el.parentElement;
if (!parent) return el.tagName.toLowerCase();
const siblings = Array.from(parent.children);
const sameTagSiblings = siblings.filter(
(sibling) => sibling.tagName === el.tagName
);
if (sameTagSiblings.length === 1) {
return `${getElementPath(parent)} > ${el.tagName.toLowerCase()}`;
} else {
const index = sameTagSiblings.indexOf(el) + 1;
return `${getElementPath(
parent
)} > ${el.tagName.toLowerCase()}:nth-of-type(${index})`;
}
}
return getElementPath(targetElement);
}
function attemptBid() {
if (!isRunning()) return;
const bidCount = getBidCount();
const buttonSelector = getSelectedButton();
if (!buttonSelector) {
log("❌ No button selected");
stopBot();
return;
}
log(`Bid attempt #${bidCount + 1} - Looking for selected bid button...`);
const bidButton = findSelectedBidButton();
if (!bidButton) {
log(
`❌ Selected bid button not found with selector: ${buttonSelector}`
);
stopBot();
return;
}
log("✅ Found selected bid button, clicking...");
// Make sure we're clicking the actual button element
if (bidButton.tagName === "BUTTON") {
bidButton.click();
} else {
log("❌ Found element is not a button");
stopBot();
return;
}
incrementBidCount();
// Wait for confirmation dialog
setTimeout(() => {
if (isRunning()) {
handleBidConfirmation();
}
}, 100);
}
function handleBidConfirmation() {
if (!isRunning()) return;
log("🔍 Looking for bid confirmation dialog...");
// Look for the Ok button in the confirmation dialog
const okButton = document.querySelector(
".ui-dialog-buttonset button.ui-button"
);
if (!okButton) {
log("❌ No confirmation dialog found, retrying bid...");
setTimeout(() => {
if (isRunning()) {
attemptBid();
}
}, 250);
return;
}
log("✅ Found confirmation dialog, clicking Ok...");
okButton.click();
// Wait for result dialog
setTimeout(() => {
if (isRunning()) {
handleBidResult();
}
}, 250);
}
function handleBidResult() {
if (!isRunning()) return;
log("🔍 Checking bid result...");
// Look for the result dialog
const dialogContent = document.querySelector("#dialog.ui-dialog-content");
if (!dialogContent) {
log("❌ No result dialog found, retrying bid...");
setTimeout(() => {
if (isRunning()) {
attemptBid();
}
}, 250);
return;
}
const dialogText = dialogContent.textContent.trim();
log(`Dialog text: "${dialogText}"`);
// Check what type of result we got
if (dialogText.includes("You were outbid")) {
log("🔄 Outbid! Closing dialog and trying again...");
handleOutbid();
} else if (dialogText.includes("You don't have enough Credits")) {
log("💰 Out of credits! Waiting 30 seconds before retry...");
handleOutOfCredits();
} else {
log(`❓ Unknown dialog result: "${dialogText}"`);
// Try to close any dialog and retry
closeDialog();
setTimeout(() => {
if (isRunning()) {
attemptBid();
}
}, 1000);
}
}
function handleOutbid() {
if (!isRunning()) return;
// Close the outbid dialog
closeDialog();
// Immediate retry
setTimeout(() => {
if (isRunning()) {
attemptBid();
}
}, 250);
}
function handleOutOfCredits() {
if (!isRunning()) return;
// Close the credits dialog
closeDialog();
// Wait 30 seconds before retry
log("⏰ Waiting 30 seconds for more credits...");
retryTimeout = setTimeout(() => {
if (isRunning()) {
log("🔄 Retrying after credit wait...");
attemptBid();
}
}, 30000);
}
function closeDialog() {
// Try multiple selectors to find and click the Ok button
const okSelectors = [
".ui-dialog-buttonset button.ui-button",
".ui-dialog-buttonpane button.ui-button",
'.ui-dialog button[type="button"]:last-child',
];
for (const selector of okSelectors) {
const okButton = document.querySelector(selector);
if (okButton && okButton.textContent.trim() === "Ok") {
log("✅ Closing dialog...");
okButton.click();
return true;
}
}
// Fallback: try to find any Ok button
const allButtons = document.querySelectorAll("button.ui-button");
for (const button of allButtons) {
if (button.textContent.trim() === "Ok") {
log("✅ Found Ok button, closing dialog...");
button.click();
return true;
}
}
log("❌ Could not find Ok button to close dialog");
return false;
}
function main() {
if (!isRunning()) return;
log("🚀 Bidder main executing...");
// Check if we're on an auction page
if (!location.href.includes("auction=")) {
log("❌ Not on auction page, stopping bidder");
stopBot();
return;
}
// Check if we have a selected button
const selectedButton = getSelectedButton();
if (!selectedButton) {
log("📍 No button selected, please select a bid button");
return;
}
// If we don't have any pending operations, start bidding
const hasDialog = document.querySelector(".ui-dialog");
if (!hasDialog) {
log("📍 No active dialogs, starting bid attempt...");
setTimeout(() => {
if (isRunning()) {
attemptBid();
}
}, 100);
} else {
log("📍 Dialog detected, handling result...");
setTimeout(() => {
if (isRunning()) {
handleBidResult();
}
}, 100);
}
}
function getStatus() {
if (!isRunning()) {
return "Stopped";
}
const bidCount = getBidCount();
const hasButton = getSelectedButton() ? "✅" : "❌";
return `Running - ${bidCount} bids placed ${hasButton}`;
}
return {
startBot,
stopBot,
main,
getStatus,
isRunning: isRunning,
};
})();
function createControls() {
// 1) If the panel already exists, do nothing
if (document.getElementById("bot-controls")) return;
// 2) Inject a <style> block with modern/tabbed styles
const css = `
/* Container panel */
#bot-controls {
position: fixed;
bottom: 20px;
left: 20px;
width: 250px;
background: rgba(0, 0, 0, 0.8);
color: #fff;
border-radius: 8px;
font-family: "Segoe UI", Tahoma, sans-serif;
font-size: 13px;
z-index: 9999;
user-select: none;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
}
/* Header bar (drag handle + title + toggle) */
#bot-header {
padding: 8px 12px;
border-bottom: 1px solid #444;
display: flex;
align-items: center;
justify-content: space-between;
cursor: move;
}
#bot-header span {
display: inline-flex;
align-items: center;
}
#status-dot {
width: 8px;
height: 8px;
border-radius: 50%;
background: #e74c3c; /* softer red */
margin-right: 6px;
flex-shrink: 0;
}
/* Collapse / Expand toggle */
#collapse-toggle {
cursor: pointer;
font-size: 12px;
transition: transform 0.2s ease, color 0.2s ease;
}
#collapse-toggle:hover {
color: #ddd;
}
/* Main content area */
#bot-content {
padding: 10px;
}
/* “Progress” text */
#friend-progress {
display: block;
margin-bottom: 6px;
font-size: 12px;
color: #ccc;
}
/* Queue visualizer box */
#queue-visualizer {
margin: 6px 0;
padding: 6px;
background: rgba(255, 255, 255, 0.1);
border-radius: 4px;
}
.queue-counter {
font-size: 12px;
color: #ccc;
transition: transform 0.15s ease;
}
#queue-visualizer span:last-child {
font-size: 10px;
color: #999;
}
/* Generic button style */
.bot-button {
margin: 2px;
padding: 5px 10px;
font-size: 12px;
background: #333;
border: none;
border-radius: 4px;
color: #fff;
cursor: pointer;
transition: background 0.2s ease;
}
.bot-button:hover {
background: #444;
}
/* Section wrapper style */
.bot-section {
margin-top: 10px;
padding-top: 8px;
border-top: 1px solid #444;
}
.bot-section-header {
margin-bottom: 5px;
font-weight: bold;
}
/* Tab bar */
#tab-bar {
display: flex;
margin-top: 10px;
border-bottom: 1px solid #444;
}
.tab-button {
flex: 1;
text-align: center;
padding: 6px 0;
cursor: pointer;
font-size: 12px;
background: #2c2c2c;
transition: background 0.2s ease;
user-select: none;
}
.tab-button:hover {
background: #3a3a3a;
}
.tab-button.active {
background: #444;
border-bottom: 2px solid #e74c3c;
}
/* Tab panels (only one visible at a time) */
.tab-panel {
display: none;
}
.tab-panel.active {
display: block;
}
/* add an ios style switch to toggle evil mode with the id evil-mode-toggle it has some margin left and the text is on the right side of the switch that says "evil mode" and the switch is a circle in a rectangular "track" where off is having the circle at the right with a green background and "on" is the circle on the right with a red background */
.switch {
position: relative;
display: inline-block;
width: 34px;
height: 20px;
}
.switch input {
opacity: 0;
width: 0;
height: 0;
}
.slider {
position: absolute;
cursor: pointer;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: #ccc;
transition: .4s;
border-radius: 34px;
}
.slider:before {
position: absolute;
content: "";
height: 20px;
width: 20px;
left: 0;
bottom: 0;
background-color: #fff;
transition: .4s;
border-radius: 34px;
}
input:checked + .slider {
background-color: #e74c3c;
}
input:checked + .slider:before {
transform: translateX(14px);
}
`;
const styleTag = document.createElement("style");
styleTag.textContent = css;
document.head.appendChild(styleTag);
// 3) Create the panel container
const panel = document.createElement("div");
panel.id = "bot-controls";
document.body.appendChild(panel);
// 4) Build out the inner HTML with tabs
panel.innerHTML = `
<div id="bot-header">
<span>
<div id="status-dot"></div>
<span>🤖 Ovipets Bot</span>
<label class="switch" style="margin-left: 10px;">
<input type="checkbox" id="evil-mode-toggle">
<span class="slider"></span>
<span style="color: #ccc; float: left; white-space: nowrap; margin-left: 40px; margin-bottom: 20px; margin-top: -20px;">Evil Mode</span>
</label>
</span>
<div id="collapse-toggle" title="Collapse panel">▼</div>
</div>
<div id="bot-content">
<!-- Progress + Queue -->
<div id="friend-progress">👥 0/0</div>
<div id="queue-visualizer">
<div style="display: flex; align-items: center; justify-content: space-between; margin-bottom: 4px;">
<span class="queue-counter">🥚 0</span>
<span>Egg Queue</span>
</div>
<!-- Bars inserted dynamically by updateQueueVisualizer() -->
</div>
<!-- Main Bot Controls (always visible) -->
<div id="main-controls" style="margin-bottom: 8px;">
<button id="start-bot" class="bot-button">▶️ START</button>
<button id="stop-bot" class="bot-button">⏹️ STOP</button>
<!-- peaceful mode checkbox -->
<label style="margin-left: 10px;">
<input type="checkbox" style="margin-bottom: -50px;padding-top: 50px;float: inline-end;"id="peaceful-mode">
<span style="color: #ccc; margin-left: 10px;">Peaceful Mode</span>
</label>
</div>
<!-- Tab Bar -->
<div id="tab-bar">
<div class="tab-button active" data-tab="old-bots">Old Bots</div>
<div class="tab-button" data-tab="my-pets">My Pets</div>
<div class="tab-button" data-tab="other">Other</div>
</div>
<!-- Tab Panels -->
<div id="tab-content">
<!-- Old Bots Panel -->
<div id="tab-old-bots" class="tab-panel active">
<div class="bot-section">
<div class="bot-section-header">🔄 Old Across</div>
<button id="old-across-start" class="bot-button">▶️ Start</button>
<button id="old-across-stop" class="bot-button">⏹️ Stop</button>
<button id="ovipets-old-across-resume" class="bot-button" style="display: none;">⏯️ Resume</button>
</div>
<div class="bot-section">
<div class="bot-section-header">🔍 Old Thorough</div>
<button id="old-thorough-start" class="bot-button">▶️ Start</button>
<button id="old-thorough-stop" class="bot-button">⏹️ Stop</button>
<button id="ovipets-old-thorough-resume" class="bot-button" style="display: none;">⏯️ Resume</button>
</div>
</div>
<!-- My Pets Panel -->
<div id="tab-my-pets" class="tab-panel">
<div class="bot-section">
<div class="bot-section-header">💕 Breeder</div>
<button id="breeder-start" class="bot-button">▶️ Start</button>
<button id="breeder-stop" class="bot-button">⏹️ Stop</button>
</div>
<div class="bot-section">
<div class="bot-section-header">🍖 Feeder</div>
<button id="feeder-start" class="bot-button">▶️ Start</button>
<button id="feeder-stop" class="bot-button">⏹️ Stop</button>
</div>
<div class="bot-section">
<div class="bot-section-header">🧬 Genetic Profiler</div>
<button id="genetic-profiler-start" class="bot-button">▶️ Start</button>
<button id="genetic-profiler-stop" class="bot-button">⏹️ Stop</button>
</div>
<div class="bot-section">
<div class="bot-section-header">📝 Namer</div>
<button id="namer-start" class="bot-button">▶️ Start</button>
<button id="namer-stop" class="bot-button">⏹️ Stop</button>
</div>
<div class="bot-section">
<div class="bot-section-header">🥚 Hatcher</div>
<button id="hatcher-start" class="bot-button">▶️ Start</button>
<button id="hatcher-stop" class="bot-button">⏹️ Stop</button>
</div>
</div>
<!-- Other Panel -->
<div id="tab-other" class="tab-panel">
<div class="bot-section">
<div class="bot-section-header">📨 Befriender</div>
<button id="befriender-start" class="bot-button">▶️ Start</button>
<button id="befriender-stop" class="bot-button">⏹️ Stop</button>
</div>
<div class="bot-section">
<div class="bot-section-header">🏠 Adopter</div>
<button id="adopter-start" class="bot-button">▶️ Start</button>
<button id="adopter-stop" class="bot-button">⏹️ Stop</button>
</div>
<div class="bot-section">
<div class="bot-section-header">📸 Image Collector</div>
<button id="image-collector-start" class="bot-button">▶️ Start</button>
<button id="image-collector-stop" class="bot-button">⏹️ Stop</button>
</div>
<div class="bot-section">
<div class="bot-section-header">🔨 Bidder</div>
<button id="bidder-start" class="bot-button">▶️ Start</button>
<button id="bidder-stop" class="bot-button">⏹️ Stop</button>
</div>
</div>
</div>
</div>
`;
// 5) Add collapse/expand functionality
let isCollapsed = false;
const collapseToggle = document.getElementById("collapse-toggle");
const botContent = document.getElementById("bot-content");
const evilModeToggle = document.getElementById("evil-mode-toggle");
const peacefulModeToggle = document.getElementById("peaceful-mode");
collapseToggle.onclick = (e) => {
e.stopPropagation(); // Don’t trigger drag when clicking toggle
isCollapsed = !isCollapsed;
if (isCollapsed) {
botContent.style.display = "none";
collapseToggle.textContent = "▲";
collapseToggle.title = "Expand panel";
} else {
botContent.style.display = "block";
collapseToggle.textContent = "▼";
collapseToggle.title = "Collapse panel";
}
};
// 6) Add drag functionality (header acts as grab handle)
let isDragging = false;
let dragStartX = 0,
dragStartY = 0;
let panelStartX = 0,
panelStartY = 0;
const header = document.getElementById("bot-header");
header.onmousedown = (e) => {
// Don’t start if user clicked the collapse toggle
if (e.target.id === "collapse-toggle") return;
isDragging = true;
dragStartX = e.clientX;
dragStartY = e.clientY;
const rect = panel.getBoundingClientRect();
panelStartX = rect.left;
panelStartY = rect.top;
// Show grabbing cursor
document.body.style.cursor = "grabbing";
header.style.cursor = "grabbing";
e.preventDefault();
};
document.onmousemove = (e) => {
if (!isDragging) return;
const deltaX = e.clientX - dragStartX;
const deltaY = e.clientY - dragStartY;
let newX = panelStartX + deltaX;
let newY = panelStartY + deltaY;
// Keep panel within viewport
const maxX = window.innerWidth - panel.offsetWidth;
const maxY = window.innerHeight - panel.offsetHeight;
newX = Math.max(0, Math.min(newX, maxX));
newY = Math.max(0, Math.min(newY, maxY));
panel.style.left = newX + "px";
panel.style.top = newY + "px";
panel.style.bottom = "auto"; // Clear bottom when dragging
};
document.onmouseup = () => {
if (isDragging) {
isDragging = false;
document.body.style.cursor = "";
header.style.cursor = "move";
}
};
evilModeToggle.onclick = () => {
isEvil = evilModeToggle.classList.toggle("active");
log(`Evil mode is now ${isEvil ? "ON" : "OFF"}`);
// make the panel have a deep red background when evil mode is on, with the same transparency though
panel.style.background = isEvil
? "rgba(139, 0, 0, 0.8)" // Dark red with transparency
: "rgba(0, 0, 0, 0.8)"; // Default black with transparency
};
peacefulModeToggle.onclick = () => {
isPeaceful = peacefulModeToggle.checked;
log(`Peaceful mode is now ${isPeaceful ? "ON" : "OFF"}`);
};
// 7) Wire up all button event listeners exactly as before
// Main bot controls
document.getElementById("start-bot").onclick = startBot;
document.getElementById("stop-bot").onclick = stopBot;
// Old Across
document.getElementById("old-across-start").onclick =
OvipetsOldAcross.startBot;
document.getElementById("old-across-stop").onclick =
OvipetsOldAcross.stopBot;
document.getElementById("ovipets-old-across-resume").onclick =
OvipetsOldAcross.resumeBot;
// Old Thorough
document.getElementById("old-thorough-start").onclick =
OvipetsOldThorough.startBot;
document.getElementById("old-thorough-stop").onclick =
OvipetsOldThorough.stopBot;
document.getElementById("ovipets-old-thorough-resume").onclick =
OvipetsOldThorough.resumeBot;
// Breeder
document.getElementById("breeder-start").onclick = Breeder.startBot;
document.getElementById("breeder-stop").onclick = Breeder.stopBot;
// Feeder
document.getElementById("feeder-start").onclick = Feeder.startBot;
document.getElementById("feeder-stop").onclick = Feeder.stopBot;
// Genetic Profiler
document.getElementById("genetic-profiler-start").onclick =
GeneticProfiler.startBot;
document.getElementById("genetic-profiler-stop").onclick =
GeneticProfiler.stopBot;
// Namer
document.getElementById("namer-start").onclick = Namer.startBot;
document.getElementById("namer-stop").onclick = Namer.stopBot;
// Befriender
document.getElementById("befriender-start").onclick = Befriender.startBot;
document.getElementById("befriender-stop").onclick = Befriender.stopBot;
// Adopter
document.getElementById("adopter-start").onclick = Adopter.startBot;
document.getElementById("adopter-stop").onclick = Adopter.stopBot;
document.getElementById("hatcher-start").onclick = Hatcher.startBot;
document.getElementById("hatcher-stop").onclick = Hatcher.stopBot;
// In the createControls function, add these event listeners:
// Bidder
document.getElementById("bidder-start").onclick = Bidder.startBot;
document.getElementById("bidder-stop").onclick = Bidder.stopBot;
// Image Collector
document.getElementById("image-collector-start").onclick =
ImageCollector.startBot;
document.getElementById("image-collector-stop").onclick =
ImageCollector.stopBot;
// 8) Tab‐switching logic
const tabButtons = Array.from(document.querySelectorAll(".tab-button"));
const tabPanels = {
"old-bots": document.getElementById("tab-old-bots"),
"my-pets": document.getElementById("tab-my-pets"),
other: document.getElementById("tab-other"),
};
tabButtons.forEach((btn) => {
btn.addEventListener("click", () => {
const target = btn.getAttribute("data-tab");
// 1. Toggle active class on buttons
tabButtons.forEach((b) => b.classList.remove("active"));
btn.classList.add("active");
// 2. Show/hide panels
Object.keys(tabPanels).forEach((key) => {
if (key === target) {
tabPanels[key].classList.add("active");
} else {
tabPanels[key].classList.remove("active");
}
});
});
});
// 9) Initialize status indicator & queue, and resume progress if running
log("Controls created successfully");
updateStatusIndicator();
updateQueueVisualizer();
if (isRunning()) {
log("Bot already running, resuming progress updates");
updateProgress();
pollInterval = setInterval(() => {
pollForEggs();
updateProgress();
}, 3000);
}
}
// Make sure controls are created when DOM is ready
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", () => {
createControls();
restoreState(); // Restore state after controls are created
});
} else {
createControls();
restoreState(); // Restore state immediately if DOM is already ready
}
// Also try on window load as backup
window.addEventListener("load", () => {
createControls();
Befriender.main();
Adopter.main();
Breeder.main();
Namer.main(); // Add this line
Hatcher.main();
Bidder.main();
Feeder.main();
GeneticProfiler.main();
OvipetsOldAcross.main();
OvipetsOldThorough.main();
});
window.addEventListener("hashchange", () => {
Befriender.main();
Adopter.main();
Breeder.main();
Namer.main(); // Add this line
Hatcher.main();
Bidder.main();
Feeder.main();
GeneticProfiler.main();
OvipetsOldAcross.main();
OvipetsOldThorough.main();
});
})();