FarmRPG - Quick Tasks

Adds buttons to easily complete farm tasks, PHRs and exchange cneter trades with a single click.. Visit farm supply to confirm you have unlocked the perks for chickens, cows and pigs for farm tasks.

Pada tanggal 19 April 2025. Lihat %(latest_version_link).

// ==UserScript==
// @name         FarmRPG - Quick Tasks
// @namespace    duck.wowow
// @version      0.1.1
// @description  Adds buttons to easily complete farm tasks, PHRs and exchange cneter trades with a single click.. Visit farm supply to confirm you have unlocked the perks for chickens, cows and pigs for farm tasks.
// @author       Odung
// @match        https://farmrpg.com/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=farmrpg.com
// @grant        GM.getValue
// @grant        GM.setValue
// @require      https://cdn.jsdelivr.net/npm/[email protected]/build/global/luxon.min.js
// @license      MIT
// ==/UserScript==

(async function() {
    'use strict';

    const now = luxon.DateTime.now().setZone("America/Chicago").startOf("day");
    const current = now.day;

    let perks = await GM.getValue('perks', {});

    function observePage(element) {
        const observer = new MutationObserver((mutationsList) => {
            for (const mutation of mutationsList) {
                if (mutation.type === 'attributes' && mutation.attributeName === 'data-page') {
                    const page = mutation.target.getAttribute('data-page');
                    if (page === 'xfarm') farm();
                    else if (page === 'supply') supply();
                    else if (page === 'quests') quests();
                    else if (page === 'exchange') exchange();
                }
            }
        });

        observer.observe(element, { attributes: true });
    }

    async function farm() {
        const id = getFarmId();
        if (!id) return;

        const farmData = await GM.getValue('farm', {chicken: 0, cow: 0, pig: 0, storehouse: 0, farmhouse: 0, raptor: 0});

        function createButton(title, url, id, selector) {
            const button = document.createElement("button");
            button.textContent = title;
            button.id = id;
            button.style.cssText = `display: flex; width: 50px; align-items: center; justify-content: center; padding: 8px 12px; margin-left: 10px; font-size: 14px; font-weight: bold; border: none; border-radius: 5px; background: #333; color: #fff; cursor: pointer; transition: background 0.2s ease, transform 0.1s ease;`;

            button.addEventListener("mouseenter", () => {
                button.style.background = "#444";
                button.style.transform = "scale(1.05)";
            });

            button.addEventListener("mouseleave", () => {
                button.style.background = "#333";
                button.style.transform = "scale(1)";
            });

            button.addEventListener("click", async (e) => {
                e.preventDefault();
                e.stopPropagation();
                try {
                    const response = await fetch(url, { method: "POST" });
                    console.log(response);

                    if (response.ok) {
                        farmData[id] = current;
                        await GM.setValue('farm', farmData);
                        button.remove();
                        const calendar = document.querySelector(selector)?.parentElement.querySelector('.f7-icons')?.parentElement;
                        if (calendar) calendar.remove();
                    } else {
                        button.textContent = 'Error';
                        setTimeout(() => {
                            button.textContent = title;
                        }, 1000);
                    }
                } catch (error) {
                    console.error(error);
                    button.textContent = 'Error';
                    setTimeout(() => {
                        button.textContent = title;
                    }, 1000);
                }
            });

            return button;
        }

        const observer = new MutationObserver(() => {
            const elements = [
                { selector: 'div.page:nth-child(2) > div:nth-child(1) > div:nth-child(2) > div:nth-child(7) > div:nth-child(1) > div:nth-child(1) > ul:nth-child(1) > li:nth-child(1) > a:nth-child(1) > div:nth-child(1) > div:nth-child(1)', title: "Pet", url: "https://farmrpg.com/worker.php?go=petallchickens", id: 'chicken' },
                { selector: 'div.page:nth-child(2) > div:nth-child(1) > div:nth-child(2) > div:nth-child(7) > div:nth-child(1) > div:nth-child(1) > ul:nth-child(1) > li:nth-child(2) > a:nth-child(1) > div:nth-child(1) > div:nth-child(1)', title: "Pet", url: "https://farmrpg.com/worker.php?go=petallcows", id: 'cow' },
                { selector: 'div.page:nth-child(2) > div:nth-child(1) > div:nth-child(2) > div:nth-child(7) > div:nth-child(1) > div:nth-child(1) > ul:nth-child(1) > li:nth-child(3) > a:nth-child(1) > div:nth-child(1) > div:nth-child(1)', title: "Feed", url: "https://farmrpg.com/worker.php?go=feedallpigs", id: 'pig' },
                { selector: 'div.page:nth-child(2) > div:nth-child(1) > div:nth-child(2) > div:nth-child(7) > div:nth-child(1) > div:nth-child(1) > ul:nth-child(1) > li:nth-child(4) > a:nth-child(1) > div:nth-child(1) > div:nth-child(1)', title: "Work", url: `https://farmrpg.com/worker.php?go=work&id=${id}`, id: 'storehouse' },
                { selector: 'div.page:nth-child(2) > div:nth-child(1) > div:nth-child(2) > div:nth-child(7) > div:nth-child(1) > div:nth-child(1) > ul:nth-child(1) > li:nth-child(5) > a:nth-child(1) > div:nth-child(1) > div:nth-child(1)', title: "Rest", url: `https://farmrpg.com/worker.php?go=rest&id=${id}`, id: 'farmhouse' },
                { selector: 'div.page:nth-child(2) > div:nth-child(1) > div:nth-child(2) > div:nth-child(7) > div:nth-child(1) > div:nth-child(1) > ul:nth-child(1) > li:nth-child(6) > a:nth-child(1) > div:nth-child(1) > div:nth-child(1)', title: "Pet", url: "https://farmrpg.com/worker.php?go=incuallraptors", id: 'raptor' }
            ];

            let found = false;

            elements.forEach(({ selector, title, url, id }) => {
                const element = document.querySelector(selector);
                if (element) {
                    found = true;
                    if (!element.querySelector("button") && farmData[id] !== current) {
                        if (id === 'chicken' && !perks['Pet-o-Matic I']) return;
                        else if (id === 'cow' && !perks['Pet-o-Matic II']) return;
                        else if (id === 'pig' && !perks['Feed-o-Matic I']) return;
                        element.appendChild(createButton(title, url, id, selector));
                    }
                }
            });

            if (found) observer.disconnect();
        });

        observer.observe(document.body, { childList: true, subtree: true });
    }

    function getFarmId() {
        const match = window.location.href.match(/id=(\d+)/);
        return match ? match[1] : null;
    }

    async function supply() {
        const playerPerks = {};
        const requiredPerks = ['Pet-o-Matic I', 'Pet-o-Matic II', 'Feed-o-Matic I'];

        if (!Array.from(document.querySelectorAll(".item-title strong")).find(el => el.textContent.trim() === 'Pet-o-Matic I')) return;

        requiredPerks.forEach(perk => {
            if (Array.from(document.querySelectorAll(".item-title strong")).find(el => el.textContent.trim() === perk).parentElement.nextElementSibling.firstElementChild.classList.contains('btnblue')) playerPerks[perk] = true;
        });

        perks = playerPerks;
        await GM.setValue('perks', playerPerks);
    }

    function quests() {
        const phrTitle = Array.from(document.querySelectorAll('.content-block-title')).find(e => e.textContent.trim().startsWith('Personal Requests'));
        if (!phrTitle) return;

        const phrs = phrTitle.nextElementSibling?.querySelectorAll('a.item-link.close-panel') || [];

        phrs.forEach(phr => {
            const text = phr.querySelector('.item-after')?.textContent.trim();
            if (text === 'READY!') {
                const href = phr.getAttribute('href');
                const idMatch = href.match(/id=(\d+)/);
                if (!idMatch) return;
                const id = idMatch[1];

                const placement = phr.querySelector('.item-after');

                const button = document.createElement('button');
                button.textContent = 'Complete';
                button.style.cssText = `display: flex; align-items: center; justify-content: center; padding: 8px 12px; margin-right: 10px; font-size: 14px; font-weight: bold; border: none; border-radius: 5px; background: #333; color: #fff; cursor: pointer; transition: background 0.2s ease, transform 0.1s ease;`;

                button.addEventListener('click', async e => {
                    e.preventDefault();
                    e.stopPropagation();

                    try {
                        const response = await fetch(`https://farmrpg.com/worker.php?go=collectquest&id=${id}`, {method: 'POST'});
                        console.log(response);

                        if (response.ok) {
                            button.remove();
                            placement.closest('li')?.remove();
                            phrTitle.textContent = phrTitle.textContent.replace(/\((\d+)\)/, (_, n) => `(${n - 1})`);
                        } else {
                            button.textContent = 'Error';
                            setTimeout(() => {
                                button.textContent = 'Complete';
                            }, 1000);
                        }
                    } catch (error) {
                        console.error(error);
                        button.textContent = 'Error';
                        setTimeout(() => {
                            button.textContent = 'Complete';
                        }, 1000);
                    }
                });

                if (placement) placement.prepend(button);
            }
        });
    }

    function exchange() {
        const buttons = Array.from(document.querySelectorAll('.button.btngreen.acceptbtn'));
        const ids = buttons.map(btn => btn.dataset.id);

        const trades = {};

        buttons.forEach(btn => {
            const id = btn.dataset.id;
            const get = btn.dataset.get;
            const req = btn.dataset.req;

            trades[id] = { get, req };
        });

        async function disableTrades() {
            const disabledTrades = await GM.getValue('disabled-trades', {});
            const contentBlocks = document.querySelectorAll('.page-content .content-block');
            if (!contentBlocks[1] || document.querySelector('.odung-ec')) return;

            const container = document.createElement('div');
            container.className = 'odung-ec';
            container.style.cssText = 'color:#fff;background:#0c131d';

            const header = document.createElement('div');
            header.textContent = 'Disable Trades';
            header.style.cssText = 'border:1px solid #666;background:#1a1b1d;padding:5px;cursor:pointer;';

            const list = document.createElement('div');
            list.style.cssText = 'display:none;margin-top:5px;';
            header.addEventListener('click', () => {
                list.style.display = list.style.display === 'none' ? 'block' : 'none';
            });

            const toggleTrades = () => {
                buttons.forEach(btn => {
                    const id = btn.dataset.id, get = btn.dataset.get, req = btn.dataset.req;
                    const match = disabledTrades[id];
                    const shouldHide = match && match.get === get && match.req === req;
                    btn.style.display = shouldHide ? 'none' : '';
                    if (btn.previousElementSibling) btn.previousElementSibling.style.display = shouldHide ? 'none' : '';
                });
            };

            const createCheckbox = (id, trade, isChecked) => {
                const row = document.createElement('div');
                const checkbox = document.createElement('input');
                checkbox.type = 'checkbox';
                checkbox.checked = isChecked;
                checkbox.addEventListener('change', async () => {
                    if (checkbox.checked) disabledTrades[id] = trade;
                    else delete disabledTrades[id];
                    await GM.setValue('disabled-trades', disabledTrades);
                    toggleTrades();
                });
                const label = document.createElement('label');
                label.textContent = ` ${trade.req} > ${trade.get}`;
                label.prepend(checkbox);
                row.appendChild(label);
                list.appendChild(row);
            };

            Object.entries(disabledTrades).forEach(([id, trade]) => createCheckbox(id, trade, true));
            Object.entries(trades).forEach(([id, trade]) => {
                if (!disabledTrades[id]) createCheckbox(id, trade, false);
            });

            container.append(header, list);
            contentBlocks[1].prepend(container);
            toggleTrades();
        }

        disableTrades();

        buttons.forEach((btn, i) => {
            const row = btn.previousElementSibling?.querySelector('.row');
            if (!row) return;

            const children = Array.from(row.children);

            children.forEach(child => {
                child.classList.add('odung-33');
            });

            if (children.length === 2) {
                const newButton = document.createElement('button');
                newButton.textContent = 'Quick Accept';
                newButton.className = 'odung-trade-btn';
                newButton.style.cssText = 'margin:0 5px;padding:4px 8px;font-size:12px;background:#003300;color:#fff;border:1px solid #006600;cursor:pointer;align-self:center';

                newButton.addEventListener('click', async e => {
                    try {
                        const response = await fetch(`https://farmrpg.com/worker.php?go=exchtradeaccept&id=${ids[i]}`, { method: 'POST'});
                        console.log(response);

                        if (response.ok) {
                            newButton.remove();
                            btn.outerHTML = '<a href="#" class="button btngray" style="margin-bottom:15px">Trade Complete</a>';
                        } else {
                            newButton.textContent = 'Error';
                            setTimeout(() => {
                                newButton.textContent = 'Quick Accept';
                            }, 1000);
                        }
                    } catch (error) {
                        console.error(error);
                        newButton.textContent = 'Error';
                        setTimeout(() => {
                            newButton.textContent = 'Quick Accept';
                        }, 1000);
                    }
                });

                row.insertBefore(newButton, children[1]);
            }
        });

        const style = document.createElement('style');
        style.textContent = `.odung-33 { width: calc((100% - 15px*1) / 3) !important; }`;
        document.head.appendChild(style);
    }

    const observer = new MutationObserver(mutations => {
        const element = document.querySelector('#fireworks');
        if (element) {
            observePage(element);
            observer.disconnect();
        }
    });

    observer.observe(document.body, { childList: true, subtree: true });
})();