Skip to content

Instantly share code, notes, and snippets.

@anuragl94
Last active October 7, 2025 16:43
Show Gist options
  • Select an option

  • Save anuragl94/5055b966ce2e6f848c721271c7434ec1 to your computer and use it in GitHub Desktop.

Select an option

Save anuragl94/5055b966ce2e6f848c721271c7434ec1 to your computer and use it in GitHub Desktop.
// ==UserScript==
// @name AutoFarm RPG
// @namespace automatron
// @version 0.1.0
// @description Farm RPG shortcuts to make your life easy
// @author Automatron
// @match https://farmrpg.com/*
// @match https://alpha.farmrpg.com/*
// @icon https://www.google.com/s2/favicons?sz=64&domain=farmrpg.com
// @license MIT
// @grant none
// ==/UserScript==
(function(window) {
'use strict';
const DEBUG_MODE = true;
const STYLE = `
@scope {
:scope {
--border-color: hsl(0 0% 40% / 1);
--bg-color: hsl(220deg 5% 8% / 80%);
position: fixed;
border: 1px solid var(--border-color);
border-radius: 2px;
background-color: var(--bg-color);
backdrop-filter: blur(4px);
min-height: 240px;
min-width: 320px;
color: #fff;
padding: 32px 16px;
&.movable {
position: fixed;
top: 0;
left: 0;
z-index: 999999;
}
.tray-handle {
cursor: move;
position: absolute;
top: 0;
left: 0;
padding: 4px;
&::after {
display: block;
content: "";
width: 16px;
aspect-ratio: 1/1;
background-image: linear-gradient(to bottom right, var(--border-color) 25%, transparent 25%, transparent 50%, var(--border-color) 50%, var(--border-color) 55%, transparent 55%);
}
}
.tray-actions {
margin-bottom: 32px;
}
.tray-abort {
margin-top: 8px;
}
button {
cursor: pointer;
}
}}`;
// Dataset
const TOWNSFOLK = {
"Baba Gec": {
"likes": [
"Leek",
"Onion",
"Rope",
"Snail"
],
"loves": [
"Cabbage Stew",
"Peach Juice",
"Wooden Button"
],
"mailbox": "267531",
"img": "https://farmrpg.com/img/items/merchant.png"
},
"Beatrix": {
"likes": [
"Bird Egg",
"Carbon Sphere",
"Coal",
"Hammer",
"Hops",
"Oak"
],
"loves": [
"Black Powder",
"Explosive",
"Fireworks",
"Iced Tea"
],
"mailbox": "22440",
"img": "https://farmrpg.com/img/items/a_011.png"
},
"Borgen": {
"likes": [
"Glass Orb",
"Gold Carrot",
"Gold Cucumber",
"Gold Peas",
"Milk",
"Slimestone"
],
"loves": [
"Cheese",
"Gold Catfish",
"Wooden Box"
],
"mailbox": "53900",
"img": "https://farmrpg.com/img/items/borgen.png"
},
"Buddy": {
"likes": [
"Bone",
"Bucket",
"Giant Centipede",
"Gold Peppers",
"Gummy Worms",
"Mushroom",
"Snail",
"Spider"
],
"loves": [
"Pirate Bandana",
"Pirate Flag",
"Purple Flower",
"Valentines Card"
],
"mailbox": "22447",
"img": "https://farmrpg.com/img/items/buddy.png"
},
"Cpt Thomas": {
"likes": [
"Blue Crab",
"Minnows"
],
"loves": [
"Fishing Net",
"Gold Catfish",
"Gold Drum",
"Gold Trout",
"Large Net"
],
"mailbox": "71805",
"img": "https://farmrpg.com/img/items/MustacheTom96.png"
},
"Cecil": {
"likes": [
"Aquamarine",
"Giant Centipede",
"Grapes",
"Ladder",
"Slimestone",
"Snail"
],
"loves": [
"Grasshopper",
"Horned Beetle",
"Leather",
"MIAB",
"Old Boot",
"Shiny Beetle",
"Yarn"
],
"mailbox": "22442",
"img": "https://farmrpg.com/img/items/a_027.png"
},
"Charles": {
"likes": [
"3-leaf Clover",
"Carrot",
"Grasshopper",
"Twine"
],
"loves": [
"Apple",
"Apple Cider",
"Box of Chocolate 01",
"Gold Carrot",
"Peach",
"Valentines Card"
],
"mailbox": "71760",
"img": "https://farmrpg.com/img/items/npc_horse.png"
},
"Cid": {
"likes": [
"Black Powder",
"Blue Feathers",
"Shimmer Stone",
"Stone"
],
"loves": [
"Bomb",
"Diamonds",
"Explosive",
"Mushroom Stew",
"Safety Goggles",
"Spider"
],
"mailbox": "16",
"img": "https://farmrpg.com/img/items/cid.png"
},
"frank": {
"likes": [
"Blue Dye",
"Blue Feathers",
"Bucket",
"Caterpillar",
"Feathers",
"Grasshopper"
],
"loves": [
"Carrot",
"Gold Carrot"
],
"mailbox": "84518",
"img": "https://farmrpg.com/img/items/npc_bunny1.png"
},
"Gary Bearson V": {
"likes": [
"Feathers",
"Oak",
"Trout"
],
"loves": [
"Apple Cider",
"Gold Trout",
"Yarn",
"You Rock Card"
],
"mailbox": "38",
"img": "https://farmrpg.com/img/items/bear_01.png"
},
"Geist": {
"likes": [
"Blue Crab",
"Green Chromis",
"Stingray",
"Yellow Perch"
],
"loves": [
"Gold Catfish",
"Goldgill",
"Sea Pincher Special",
"Shrimp-a-Plenty"
],
"mailbox": "118065",
"img": "https://farmrpg.com/img/items/npc_beast.png"
},
"George": {
"likes": [
"Arrowhead",
"Bird Egg",
"Glass Orb",
"Hops",
"Mushroom Stew",
"Orange Juice"
],
"loves": [
"Apple Cider",
"Carbon Sphere",
"Hide",
"Mug of Beer",
"Spider"
],
"mailbox": "22443",
"img": "https://farmrpg.com/img/items/a_034.png"
},
"Holger": {
"likes": [
"Apple Cider",
"Arrowhead",
"Bluegill",
"Carp",
"Cheese",
"Horn",
"Largemouth Bass",
"Mushroom Stew",
"Peach",
"Peas",
"Trout"
],
"loves": [
"Gold Trout",
"Mug of Beer",
"Potato",
"Wooden Table"
],
"mailbox": "22439",
"img": "https://farmrpg.com/img/items/a_028.png"
},
"Jill": {
"likes": [
"Cheese",
"Grapes",
"Milk",
"Old Boot",
"Scrap Metal",
"Tomato"
],
"loves": [
"Leather",
"MIAB",
"Mushroom Paste",
"Peach",
"Yellow Perch"
],
"mailbox": "22444",
"img": "https://farmrpg.com/img/items/a_024.png"
},
"Lorn": {
"likes": [
"3-leaf Clover",
"Apple Cider",
"Bucket",
"Green Parchment",
"Iced Tea",
"Iron Cup",
"Peas",
"Purple Parchment"
],
"loves": [
"Glass Orb",
"Gold Peas",
"Milk",
"Shrimp",
"Small Prawn"
],
"mailbox": "22446",
"img": "https://farmrpg.com/img/items/a_088.png"
},
"Mariya": {
"likes": [
"Cucumber",
"Eggplant",
"Eggs",
"Iced Tea",
"Milk",
"Peach",
"Radish"
],
"loves": [
"Cat's Meow",
"Leather Diary",
"Mushroom Stew",
"Onion Soup",
"Over The Moon",
"Quandary Chowder",
"Sea Pincher Special",
"Shrimp-a-Plenty"
],
"mailbox": "178572",
"img": "https://farmrpg.com/img/items/mariya.png"
},
"Mummy": {
"likes": [
"Fish Bones",
"Hammer",
"Treat Bag 02",
"Yarn"
],
"loves": [
"Bone",
"Spider",
"Valentines Card"
],
"mailbox": "70604",
"img": "https://farmrpg.com/img/items/mummy_t_01.png"
},
"Ric Ryph": {
"likes": [
"Arrowhead",
"Black Powder",
"Bucket",
"Carbon Sphere",
"Coal",
"Green Parchment",
"Old Boot",
"Unpolished Shimmer Stone"
],
"loves": [
"5 Gold",
"Hammer",
"Mushroom Paste",
"Shovel"
],
"mailbox": "59421",
"img": "https://farmrpg.com/img/items/npc_figure2.png"
},
"ROOMBA": {
"likes": [
"Glass Orb",
"Hammer",
"Scrap Wire"
],
"loves": [
"Carbon Sphere",
"Scrap Metal"
],
"mailbox": "71761",
"img": "https://farmrpg.com/img/items/robot_02.png"
},
"Rosalie": {
"likes": [
"Apple",
"Apple Cider",
"Aquamarine",
"Carrot",
"Caterpillar",
"Fireworks",
"Iced Tea",
"Purple Flower"
],
"loves": [
"Blue Dye",
"Box of Chocolate 01",
"Gold Carrot",
"Green Dye",
"Purple Dye",
"Red Dye",
"Valentines Card"
],
"mailbox": "22438",
"img": "https://farmrpg.com/img/items/a_098.png"
},
"Star Meerif": {
"likes": [
"Eggs",
"Feathers"
],
"loves": [
"Blue Feathers",
"Gold Feather"
],
"mailbox": "46158",
"img": "https://farmrpg.com/img/items/npc_figure.png"
},
"Thomas": {
"likes": [
"Carp",
"Drum",
"Gummy Worms",
"Iced Tea",
"Largemouth Bass",
"Mealworms",
"Minnows"
],
"loves": [
"Fishing Net",
"Flier",
"Gold Catfish",
"Gold Trout",
"Goldgill"
],
"mailbox": "22441",
"img": "https://farmrpg.com/img/items/a_048.png"
},
"Vincent": {
"likes": [
"Acorn",
"Apple",
"Cheese",
"Hops",
"Horn",
"Leather Diary",
"Shovel",
"Wooden Box"
],
"loves": [
"5 Gold",
"Apple Cider",
"Axe",
"Lemonade",
"Mushroom Paste",
"Onion Soup",
"Orange Juice"
],
"mailbox": "22445",
"img": "https://farmrpg.com/img/items/a_047.png"
}
};
// { crop_id: seed_id }
const SEEDS_MAP = {
11: 12, // pepper
};
// Utils
function newEl(tag, classes, content) {
const el = document.createElement(tag);
if (classes) {
el.classList.add(...classes.split(" ").map(s => s.trim()).filter(Boolean));
}
if (content) {
el.innerHTML = content;
}
return el;
}
async function addDelay(minDelay, maxDelay = minDelay, abortSignal) {
if (maxDelay < minDelay) {
maxDelay = minDelay;
}
const delay = Math.floor(minDelay + Math.random() * (maxDelay - minDelay));
return new Promise((resolve, reject) => {
const timer = window.setTimeout(() => resolve(delay), delay);
abortSignal?.addEventListener("abort", () => {
window.clearTimeout(timer);
reject("Request aborted");
});
});
}
async function findElement(selector, subtree = document.body, abortSignal) {
const elementInExistence = document.querySelector(selector);
if (elementInExistence) {
return elementInExistence;
}
return new Promise((resolve, reject) => {
const observer = new MutationObserver((mutations, observer) => {
const element = document.querySelector(selector);
if (element) {
observer.disconnect();
resolve(element);
}
});
observer.observe(subtree, {
childList: true,
subtree: true,
attributes : true,
attributeFilter : ["style", "class"]
});
abortSignal?.addEventListener("abort", () => {
reject("Request aborted");
});
});
}
// give it a name for the script to remember the element's position even after browser reload
function makeElementMovable(element, handler, syncName) {
element.classList.add("movable");
let prevX = 0, prevY = 0, isDragging = false;
const moveEl = (dx, dy) => {
const rect = element.getBoundingClientRect();
const x = rect.left + dx;
const y = rect.top + dy;
element.style.position = "absolute";
element.style.left = x + "px";
element.style.top = y + "px";
if (syncName) {
window.localStorage.setItem(`__autofarm_pos_${syncName}`, `${x},${y}`);
}
};
const syncedValue = window.localStorage.getItem(`__autofarm_pos_${syncName}`);
if (syncedValue) {
const coords = syncedValue.split(",").map(v => Math.min(Number(v), 1000));
moveEl(...coords.map(Number));
}
handler.addEventListener("mousedown", function (e) {
isDragging = true;
prevX = e.clientX;
prevY = e.clientY;
e.preventDefault();
});
window.addEventListener("mouseup", function () {
isDragging = false;
});
window.addEventListener("mousemove", function (e) {
if (!isDragging) return;
const dx = e.clientX - prevX;
const dy = e.clientY - prevY;
moveEl(dx, dy);
prevX = e.clientX;
prevY = e.clientY;
});
}
class Overseer {
// This is a singleton class. Do not instantiate multiple times.
#tray;
#currentPage;
#flags = {}; // any page should be able to access flags - toggles may be selectively shown
#actionButtons = [];
#actionControllers = [];
#injectorScripts = [];
#abortController = new AbortController();
utils = {
findElement: async (query, subtree) => findElement(query, subtree, this.abortController.signal),
addDelay: async (min, max) => addDelay(min, max, this.abortController.signal),
abort: () => { this.#abortController.abort(); }
};
get abortController() {
if (this.#abortController.signal.aborted) {
this.#abortController = new AbortController();
}
return this.#abortController;
}
constructor() {
this.#createTray();
this.#attachHashStateListener();
}
// DOM actions
#createTray = () => {
const styleTag = newEl("style", "", STYLE);
this.#tray = newEl("div", "autofarm-tray");
this.#tray.appendChild(styleTag);
const trayHandle = newEl("div", "tray-handle");
this.#tray.appendChild(trayHandle);
const trayActionsContainer = newEl("div", "tray-actions");
this.#tray.appendChild(trayActionsContainer);
const abortButton = newEl("button", "tray-abort button btnred", "Abort All");
this.#tray.appendChild(abortButton);
abortButton.addEventListener("click", (e) => {
this.#abortController.abort();
});
document.body.appendChild(this.#tray);
makeElementMovable(this.#tray, trayHandle, "tray");
}
#attachHashStateListener = () => {
this.page = window.history.state?.url || "index.php"; // game is inconsistent with setting its own hashstate
window.history.pushState = new Proxy(window.history.pushState, {
apply: (target, thisArg, argArray) => {
// trigger here what you need
this.page = argArray[0].url;
return target.apply(thisArg, argArray);
},
});
window.history.back = new Proxy(window.history.back, {
apply: (target, thisArg, argArray) => {
// trigger here what you need
this.page = argArray[0].url;
return target.apply(thisArg, argArray);
},
});
window.history.replaceState = new Proxy(window.history.replaceState, {
apply: (target, thisArg, argArray) => {
// trigger here what you need
this.page = argArray[0].url;
return target.apply(thisArg, argArray);
},
});
window.history.go = new Proxy(window.history.go, {
apply: (target, thisArg, argArray) => {
// trigger here what you need
this.page = argArray[0].url;
return target.apply(thisArg, argArray);
},
});
}
createActionButton({
page = ".*",
icon = "Button",
flags,
actions,
condition
}) {
const button = newEl("button", "action-button button btngreen", icon);
const config = {
page,
button,
flagButtons: []
};
const actionQueue = new ActionQueue(actions.map(action => action.bind(null, this.utils, this.#flags)), condition);
actionQueue.abortController = this.#abortController;
this.#actionControllers.push(actionQueue);
button.addEventListener("click", function(e) {
const btn = e.currentTarget;
btn.setAttribute("disabled", true);
actionQueue.start().finally(() => btn.removeAttribute("disabled"));
});
if (flags?.length) {
flags.forEach(({
key, text, defaultValue
}) => {
this.#flags[key] = Boolean(defaultValue);
const containerEl = newEl("label");
const checkboxEl = newEl("input");
checkboxEl.type = "checkbox";
checkboxEl.checked = this.#flags[key];
checkboxEl.addEventListener("change", (e) => {
this.#flags[key] = e.target.checked;
});
containerEl.appendChild(checkboxEl);
const textEl = newEl("span");
textEl.innerText = text;
containerEl.appendChild(textEl);
config.flagButtons.push(containerEl);
});
}
this.#actionButtons.push(config);
this.redraw();
}
createScriptInjector({
page = "^$",
name = "Injector script",
actions
}) {
const actionQueue = new ActionQueue(actions.map(action => action.bind(null, this.utils, this.#flags)));
this.#injectorScripts.push({
page,
actions: actionQueue
});
// Run it now if the user is already on the target page
if ((new RegExp(page)).test(this.#currentPage)) {
actionQueue.start();
}
}
// Utils
redraw = () => {
// render buttons relevant to the current page only
const applicableConfigs = this.#actionButtons.filter(({ page }) => (new RegExp(page)).test(this.#currentPage));
const buttons = this.#actionButtons.filter(({ page }) => (new RegExp(page)).test(this.#currentPage)).map(({ button }) => button);
const container = this.#tray.querySelector(".tray-actions");
container.childNodes.forEach(function(item) {
container.removeChild(item);
});
const innerContainer = newEl("div");
container.appendChild(innerContainer);
applicableConfigs.forEach(({ button, flagButtons }) => {
flagButtons.forEach(flagButton => {
innerContainer.appendChild(flagButton);
});
innerContainer.appendChild(newEl("br"));
innerContainer.appendChild(button);
});
}
// Automation
get page() {
return this.#currentPage;
}
set page(value) {
this.#currentPage = value;
console.log("Page changed. Redrawing @", value);
this.redraw();
const injectorActions = this.#injectorScripts.filter(({ page }) => (new RegExp(page)).test(this.#currentPage)).map(({ actions }) => actions);
injectorActions.forEach(actionQueue => actionQueue.start());
}
}
class ActionQueue {
#queue = [];
#repeatCondition;
#aborted = false;
abortController;
constructor(tasks = [], repeatCondition = () => false) {
this.#queue = tasks;
this.#repeatCondition = repeatCondition;
}
start = async () => {
this.#aborted = false;
const abortSignal = () => this.abort();
do {
for (const task of this.#queue) {
if (this.#aborted) return;
try {
await task(abortSignal);
} catch (err) {
this.abort();
console.log("Autofarm error:", err);
break;
}
}
} while (!this.#aborted && this.#repeatCondition())
};
abort = () => {
this.#aborted = true;
};
}
class InventoryManager {
#inventory = {};
#stale = true;
constructor() {}
refresh = (checkSellables = false) => {
const url = checkSellables ? "https://farmrpg.com/store.php" : "https://farmrpg.com/workshop.php";
window.fetch(url)
.then(r => r.text())
.then(html => {
const parser = new DOMParser();
const doc = parser.parseFromString(html, "text/html");
this.#parseAndSave(doc);
this.#stale = false;
}).catch(error => {
console.error("Failed to fetch Inventory page: ", error);
})
}
purchase(itemName, qty = 1) {
if (!this.#inventory.hasOwnProperty(itemName)) {
return;
}
const { id, maxPurchasable } = this.#inventory[itemName];
qty = Math.min(qty, maxPurchasable);
return GlobalUtils.purchaseItem(id, qty).then(() => {
this.#stale = true;
});
}
sell(itemName, qty = 1) {
if (!this.#inventory.hasOwnProperty(itemName)) {
return;
}
const { id, maxPurchasable } = this.#inventory[itemName];
qty = Math.min(qty, maxPurchasable);
return GlobalUtils.sellItem(id, qty).then(() => {
this.#stale = true;
});
}
#parseAndSave(doc) {
this.#inventory = Array.from(doc.querySelectorAll("li.close-panel")).map(el => ({
id: Number(el.dataset.id),
name: el.dataset.name,
maxPurchasable: Number(el.querySelector("input.qty").dataset.max),
requirements: Array.from(document.querySelectorAll(`.res${el.dataset.id}`)).map(span => ({
item: span.nextSibling.textContent.trim(),
qty: parseInt(span.dataset.amt, 10)
})).concat(Array.from(el.querySelectorAll("span[style='color:red']")).map(n => {
const text = n.innerText.split(" ");
return {
item: text.slice(3).join(" "),
qty: Number(text[2].replaceAll(",", ""))
};
}))
})).reduce(function(acc, { name, ...rest }) {
acc[name] = { ...rest };
return acc;
}, {});
}
get stale() {
return this.stale;
}
}
const GlobalUtils = {
addDelay: addDelay,
findElement: findElement,
isCropInventoryFull: function() {
return /[a-zA-Z ]+\*/.test(document.querySelector(".seedid").selectedOptions[0].innerText);
},
getSeedCount: function() {
return Number(document.querySelector(".seedid").selectedOptions[0].innerText.match(/[0-9]/g).join(""));
},
getPlotCount: function() {
return document.querySelector("#crops").childElementCount * 4;
},
getBaitCount: function() {
return Number(document.getElementById("baitleft").innerText.match(/[0-9]/g).join(""));
},
getBaitType: function() {
return document.getElementById("baitleft").closest("div").innerText.split(":")[0];
},
getCookableMealCount: function() {
return Number(document.querySelector(".mealloadid").selectedOptions[0].innerText.match(/[0-9]/g).join(""));
},
getOvenCount: function() {
return document.querySelectorAll(`a[href^=oven]`).length;
},
purchaseItem: async function(id, qty = 1) {
const apiUrl = `worker.php?go=buyitem&id=${id}&qty=${qty}`;
return fetch(apiUrl, {
method: "POST"
});
},
sellItem: async function(id, qty = 1) {
const apiUrl = `worker.php?go=sellitem&id=${id}&qty=${qty}`;
return fetch(apiUrl, {
method: "POST"
});
},
};
const overseer = new Overseer();
const inventory = new InventoryManager();
window.inventory = inventory;
// dummy button / template
if (DEBUG_MODE) {
overseer.createActionButton({
page: ".*",
icon: `Test button`,
condition: () => true,
actions: [
async () => { console.log("Tick"); await GlobalUtils.addDelay(1000); },
async () => { console.log("Tock"); await GlobalUtils.addDelay(1000); },
]
});
}
// Farm page actions
overseer.createActionButton({
page: "xfarm",
icon: `Manage Crops`,
flags: [
{
key: "autoBuySell",
text: "Buy & Sell",
defaultValue: false
}
],
condition: () => !GlobalUtils.isCropInventoryFull() && (GlobalUtils.getSeedCount() > GlobalUtils.getPlotCount()),
actions: [
async (utils) => {
if (document.querySelector(".harvest") || document.querySelector(".cropitem")) {
console.log("Waiting for crops to be harvested first");
return; // if the last batch is waiting for a harvest, skip to the "harvest" step first
}
const canPlant = await utils.findElement(".plantseed");
if (canPlant) {
const plantBtn = await utils.findElement(".plantallbtn");
plantBtn.click();
await utils.addDelay(2000);
}
},
async (utils) => {
const canHarvest = await utils.findElement(".harvest");
if (canHarvest) {
const harvestBtn = await utils.findElement(".harvestallbtn");
harvestBtn.click();
await utils.addDelay(2000);
}
},
async (utils, flags) => {
if (!flags.autoBuySell) {
return;
}
const seedId = Number(document.querySelector("select.seedid [selected]").getAttribute("value"));
const seedMap = Object.entries(SEEDS_MAP).find(x => x[1] === seedId);
if (!seedMap || !GlobalUtils.isCropInventoryFull()) {
return;
}
const cropId = Number(seedMap[0]);
// sell
(await utils.findElement(`a[href="town.php"]`)).click();
await utils.addDelay(2000);
(await utils.findElement(`a[href="market.php"]`)).click();
await utils.addDelay(2000);
(await utils.findElement(`button.sellbtn[data-id="${cropId}"]`)).click();
await utils.addDelay(2000);
(await utils.findElement(`.actions-modal .actions-modal-button`)).click();
await utils.addDelay(1000);
(await utils.findElement(`.modal .modal-button-bold`)).click();
await utils.addDelay(1000);
// buy
(await utils.findElement(`a[href="town.php"]`)).click();
await utils.addDelay(2000);
(await utils.findElement(`a[href="store.php"]`)).click();
await utils.addDelay(2000);
(await utils.findElement(`button.cmaxbtn[data-id="${seedId}"]`)).click();
await utils.addDelay(200);
(await utils.findElement(`button.buybtnnc[data-id="${seedId}"]`)).click();
await utils.addDelay(1000);
// return
(await utils.findElement(`a[href="index.php"]`)).click();
await utils.addDelay(2000);
(await utils.findElement(`a[href^="xfarm.php"]`)).click();
await utils.addDelay(2000);
},
]
});
// Daily Farm actions
const farmAction = async (utils, pagequery, actionquery) => {
const pageLink = await utils.findElement(pagequery);
if (!pageLink.querySelector(".f7-icons")) { return; }
pageLink.click();
await utils.addDelay(2000);
const actionButton = await utils.findElement(actionquery);
actionButton.click();
await utils.addDelay(1000);
const backButton = await utils.findElement(".modal .modal-button");
backButton.click();
await utils.addDelay(2000);
};
overseer.createActionButton({
page: "xfarm|coop|pasture|pigpen|storehouse|farmhouse|pen",
icon: `Daily farmwork`,
//condition: () => document.querySelectorAll(".item-link:has(.f7-icons)").length > 0,
actions: [
async (utils) => await farmAction(utils, `.page-on-center a[href^="coop.php"]`, `.petallbtn`),
async (utils) => await farmAction(utils, `.page-on-center a[href^="pasture.php"]`, `.petallbtn`),
async (utils) => await farmAction(utils, `.page-on-center a[href^="pigpen.php"]`, `.feedallbtn`),
async (utils) => await farmAction(utils, `.page-on-center a[href^="storehouse.php"]`, `.workbtnnc`),
async (utils) => await farmAction(utils, `.page-on-center a[href^="farmhouse.php"]`, `.restbtnnc`),
async (utils) => await farmAction(utils, `.page-on-center a[href^="pen.php"]`, `.incubateallbtn`),
async (utils) => {
// guard to avoid infinite loop if there are no chores
await utils.addDelay(1000);
},
]
});
// Fish page actions
overseer.createActionButton({
page: "fishing",
icon: `Manual fishing`,
condition: () => (GlobalUtils.getBaitCount() > 0),
actions: [
async (utils) => {
if (GlobalUtils.getBaitType() === "Mealworms") {
const fish = await utils.findElement(`.fishcaught`);
fish.click();
await utils.addDelay(100, 400);
} else {
const fish = await utils.findElement(".fish.catch", document.getElementById("water"));
fish.click();
await utils.addDelay(1000, 1200);
const confirmationButton = await utils.findElement(".fishcaught");
if (confirmationButton.classList.length === 1) {
// some dumb error handling
return;
}
confirmationButton.click();
await utils.addDelay(1000);
}
}
]
});
// Kitchen page actions
overseer.createActionButton({
page: "kitchen|oven",
icon: `Auto cook`,
flags: [
{
key: "enableStir",
text: "Stir meals",
defaultValue: false
}
],
condition: () => (GlobalUtils.getCookableMealCount() > GlobalUtils.getOvenCount()),
actions: [
async (utils) => {
// Add minimum 1s duration to our loop
const kitchenLink = await utils.findElement(`a[href="kitchen.php"]`);
kitchenLink?.click();
await utils.addDelay(1000);
},
async (utils) => {
// start cooking
if (document.querySelector(`a[href^=oven] .item-title`).innerText.split("\n")[0] !== "Empty") {
// ovens are not empty
return;
}
console.log("Start cooking");
const cookAllButton = await utils.findElement(".cookallbtn");
cookAllButton.click();
await utils.addDelay(5000);
},
async (utils, flags) => {
// meal action
const optionalButtonMapping = flags.enableStir ? {
".stirbtn": ".stirmealall",
} : {};
const actionButtonMapping = {
...optionalButtonMapping,
".tastebtn": ".tastemealall",
".seasonbtn": ".seasonmealall",
".cookreadybtn": ".cookreadyallbtn"
};
// we will keep track of cooking progress via the oven page - because the kitchen page doesn't have live updates on the progress (probably an oversight)
document.querySelector(`a[href^="oven.php"]`).click();
const mealActionButton = await utils.findElement(Object.keys(actionButtonMapping).join(", "));
// go back to kitchen
document.querySelector(`a[href="x"]`).click();
await utils.addDelay(2000);
const correspondingButtonSelector = Object.entries(actionButtonMapping).find(([k, v]) => mealActionButton.classList.contains(k.replace(".", "")))[1];
const mealsBulkActionButton = await utils.findElement(correspondingButtonSelector);
await utils.addDelay(500, 1000); // humanized delay
mealsBulkActionButton.click();
await utils.addDelay(5000);
}
]
});
// --------- Injector scripts ---------
overseer.createScriptInjector({
page: "location\.php\\?type=explore",
name: `Exploration Page Utils`,
actions: [
async (utils) => {
// Add gifting shortcuts
let explorationArea = (await utils.findElement(".navbar-on-center .center", document.querySelector(".view-main"))).innerText;
explorationArea = explorationArea.slice(-4) === "info" ? explorationArea.slice(0, -6) : explorationArea;
console.log("You are looking at items that can be found in", explorationArea);
}
]
});
console.log(
"%cAutofarm running!",
"color:green;font-family:system-ui;font-size:2rem;font-weight:bold"
);
})(window);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment