Feed Thomas Reminder

Feed reminder overlay with 3 snooze modes (1hr @ hh:02, 10s, 24hr), sync, Open Sans, Snooze button, and visual mode indicators. Updated 1hr mode to resume 2 minutes past the hour. 🐾🐈🕒⏱️📅🧭🔕

θα χρειαστεί να εγκαταστήσετε μια επέκταση όπως το Tampermonkey, το Greasemonkey ή το Violentmonkey για να εγκαταστήσετε αυτόν τον κώδικα.

θα χρειαστεί να εγκαταστήσετε μια επέκταση όπως το Tampermonkey ή το Violentmonkey για να εγκαταστήσετε αυτόν τον κώδικα.

θα χρειαστεί να εγκαταστήσετε μια επέκταση όπως το Tampermonkey ή το Violentmonkey για να εγκαταστήσετε αυτόν τον κώδικα.

θα χρειαστεί να εγκαταστήσετε μια επέκταση όπως το Tampermonkey ή το Userscripts για να εγκαταστήσετε αυτόν τον κώδικα.

You will need to install an extension such as Tampermonkey to install this script.

Θα χρειαστεί να εγκαταστήσετε μια επέκταση διαχείρισης κώδικα χρήστη για να εγκαταστήσετε αυτόν τον κώδικα.

(Έχω ήδη έναν διαχειριστή κώδικα χρήστη, επιτρέψτε μου να τον εγκαταστήσω!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(Έχω ήδη έναν διαχειριστή στυλ χρήστη, επιτρέψτε μου να τον εγκαταστήσω!)

// ==UserScript==
// @name         Feed Thomas Reminder
// @namespace    https://farmrpg.com/
// @version      2.8
// @description  Feed reminder overlay with 3 snooze modes (1hr @ hh:02, 10s, 24hr), sync, Open Sans, Snooze button, and visual mode indicators. Updated 1hr mode to resume 2 minutes past the hour. 🐾🐈🕒⏱️📅🧭🔕
// @author       Clientcoin
// @match        *://*/*
// @icon         https://farmrpg.com/favicon.ico
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_addValueChangeListener
// @grant        GM_removeValueChangeListener
// @license      Unlicense
// @homepage     https://farmrpg.com/
// @supportURL   https://farmrpg.com/
// @run-at       document-end
// ==/UserScript==

(async function () {
    'use strict';

    const FORCE_SHOW_NEXT_LOAD = false;

    const ID = 'feed-thomas-box';
    const SCALE_KEY = 'feedThomasScale';
    const STORAGE_KEY = 'feedThomasHiddenUntil';
    const MODE_KEY = 'feedThomasMode';
    const FORCE_KEY = 'feedThomasForceFlag';

    let scale = parseFloat(localStorage.getItem(SCALE_KEY)) || 1.0;
    let mode = parseInt(await GM_getValue(MODE_KEY, 0), 10);
    let hiddenUntil = parseInt(await GM_getValue(STORAGE_KEY, 0), 10) || 0;
    let clockInterval = null;

    const fontLink = document.createElement('link');
    fontLink.href = 'https://fonts.googleapis.com/css2?family=Open+Sans:wght@400;700&display=swap';
    fontLink.rel = 'stylesheet';
    document.head.appendChild(fontLink);

    if (FORCE_SHOW_NEXT_LOAD) {
        await GM_setValue(FORCE_KEY, true);
    }
    const forceOverride = await GM_getValue(FORCE_KEY, false);
    if (forceOverride) {
        hiddenUntil = 0;
        await GM_setValue(FORCE_KEY, false);
    }

    function shouldShowBox() {
        return Date.now() >= hiddenUntil;
    }

    async function handleSnooze() {
        const now = new Date();
        let next;
        if (mode === 1) {
            next = new Date(now);
            const remainder = 10 - (now.getSeconds() % 10);
            next.setSeconds(now.getSeconds() + remainder, 0);
        } else if (mode === 2) {
            next = new Date(now);
            next.setDate(now.getDate() + 1);
        } else {
            next = new Date(now);
            next.setMinutes(2, 0, 0);  // set to next hour @ 02:00
            next.setHours(next.getHours() + 1);
        }
        await GM_setValue(STORAGE_KEY, next.getTime());
    }

    function createBox() {
        if (document.getElementById(ID)) return;

        const box = document.createElement('div');
        box.id = ID;
        box.title = 'Click to snooze until next hour +2min, 10s or 24h depending on mode';
        Object.assign(box.style, {
            position: 'fixed',
            top: '10px',
            right: '10px',
            zIndex: '999999',
            backgroundColor: 'blue',
            color: 'yellow',
            fontSize: `${scale}em`,
            padding: '10px',
            borderRadius: '5px',
            cursor: 'pointer',
            display: 'flex',
            flexDirection: 'column',
            alignItems: 'center',
            textAlign: 'center',
            fontFamily: "'Open Sans', sans-serif",
            lineHeight: '1.1'
        });

        const text = document.createElement('div');
        text.textContent = 'Feed Thomas';
        text.style.fontSize = '1em';
        box.appendChild(text);

        const clock = document.createElement('div');
        clock.style.fontSize = '0.8em';
        clock.style.marginTop = '4px';
        box.appendChild(clock);

        const date = document.createElement('div');
        date.style.fontSize = '0.75em';
        box.appendChild(date);

        updateClockAndDate(clock, date);
        clockInterval = setInterval(() => updateClockAndDate(clock, date), 1000);

        const controls = document.createElement('div');
        controls.style.display = 'flex';
        controls.style.gap = '6px';
        controls.style.marginTop = '6px';

        const minusBtn = document.createElement('button');
        minusBtn.textContent = '-';
        minusBtn.title = 'Decrease size';
        styleControlButton(minusBtn);
        minusBtn.style.width = '28px';
        minusBtn.onclick = (e) => {
            e.stopPropagation();
            scale = Math.max(0.5, scale - 0.1);
            localStorage.setItem(SCALE_KEY, scale.toFixed(2));
            document.getElementById(ID).style.fontSize = `${scale}em`;
        };

        const plusBtn = document.createElement('button');
        plusBtn.textContent = '+';
        plusBtn.title = 'Increase size';
        styleControlButton(plusBtn);
        plusBtn.style.width = '28px';
        plusBtn.onclick = (e) => {
            e.stopPropagation();
            scale = Math.min(3.0, scale + 0.1);
            localStorage.setItem(SCALE_KEY, scale.toFixed(2));
            document.getElementById(ID).style.fontSize = `${scale}em`;
        };

        controls.appendChild(minusBtn);
        controls.appendChild(plusBtn);
        box.appendChild(controls);

        const modeBtn = document.createElement('button');
        updateModeButtonStyle(modeBtn, mode);
        modeBtn.title = 'Click to cycle reminder interval (1hr, 10s, 24hr)';
        modeBtn.onclick = async (e) => {
            e.stopPropagation();
            mode = (mode + 1) % 3;
            await GM_setValue(MODE_KEY, mode);
        };
        box.appendChild(modeBtn);

        const snoozeBtn = document.createElement('button');
        snoozeBtn.textContent = 'Snooze';
        snoozeBtn.title = 'Manually snooze this reminder';
        Object.assign(snoozeBtn.style, {
            marginTop: '4px',
            fontSize: '11px'
        });
        styleControlButton(snoozeBtn);
        snoozeBtn.onclick = async (e) => {
            e.stopPropagation();
            await handleSnooze();
        };
        box.appendChild(snoozeBtn);

        box.onclick = async () => {
            await handleSnooze();
        };

        document.body.appendChild(box);
    }

    function styleControlButton(btn) {
        btn.style.background = 'yellow';
        btn.style.color = 'blue';
        btn.style.border = 'none';
        btn.style.cursor = 'pointer';
        btn.style.padding = '2px 6px';
        btn.style.fontWeight = 'bold';
        btn.style.borderRadius = '4px';
        btn.style.fontFamily = "'Open Sans', sans-serif";
    }

    function updateModeButtonStyle(btn, modeValue) {
        const labels = ['Mode: 1hr', 'Mode: 10s', 'Mode: 24hr'];
        btn.textContent = labels[modeValue];
        btn.style.marginTop = '6px';
        btn.style.fontSize = '10px';

        styleControlButton(btn);

        if (modeValue === 1) {
            btn.style.opacity = '0.6';
        } else if (modeValue === 2) {
            btn.style.background = 'blue';
            btn.style.color = 'yellow';
            btn.style.opacity = '1.0';
        } else {
            btn.style.opacity = '1.0';
            btn.style.background = 'yellow';
            btn.style.color = 'blue';
        }
    }

    function updateClockAndDate(clockEl, dateEl) {
        const now = new Date();
        const timeStr = now.toLocaleTimeString([], {
            hour: '2-digit',
            minute: '2-digit',
            second: '2-digit'
        });

        const year = now.getFullYear();
        const month = now.toLocaleString('default', { month: 'short' });
        const day = String(now.getDate()).padStart(2, '0');
        const weekday = now.toLocaleString('default', { weekday: 'short' });
        const dateStr = `${year}-${month}-${day} (${weekday})`;

        clockEl.textContent = timeStr;
        dateEl.textContent = dateStr;
    }

    function checkAndShowBox() {
        const box = document.getElementById(ID);
        if (shouldShowBox()) {
            if (!box) createBox();
        } else if (box) {
            clearInterval(clockInterval);
            box.remove();
        }
    }

    GM_addValueChangeListener(STORAGE_KEY, (_, __, newValue) => {
        hiddenUntil = parseInt(newValue, 10);
        checkAndShowBox();
    });

    GM_addValueChangeListener(MODE_KEY, (_, __, newValue) => {
        mode = parseInt(newValue, 10);
        const box = document.getElementById(ID);
        if (box) {
            const btn = [...box.querySelectorAll('button')]
                .find(b => b.textContent.startsWith('Mode:'));
            if (btn) {
                updateModeButtonStyle(btn, mode);
            }
        }
    });

    function init() {
        checkAndShowBox();
        setInterval(checkAndShowBox, 2000);
    }

    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', init);
    } else {
        init();
    }
})();