Trade Chat Timer on Button

Show a timer that shows the time left to post next message.

目前為 2024-05-28 提交的版本,檢視 最新版本

// ==UserScript==
// @name         Trade Chat Timer on Button
// @namespace    http://tampermonkey.net/
// @version      1.2
// @description  Show a timer that shows the time left to post next message.
// @match        https://www.torn.com/*
// ==/UserScript==

const STORAGE_KEY = "localStorage__Trade_Chat_Timer__Do_Not_Edit";

async function waitFor(selector, parent = document) {
    return new Promise(resolve => {
        const intervalID = setInterval(() => {
            const el = parent.querySelector(selector);
            if (el) {
                clearInterval(intervalID);
                resolve(el);
            }
        }, 500);
    });
}

(async () => {
    await waitFor("head style");

    document.head.insertAdjacentHTML("beforeend", `
    <style>
        #chatRoot [class*="minimized-menu-item__"][title="Trade"].time-left {
            position: relative;
            background-size: cover;
            background-position: center;
        }
        #chatRoot [class*="minimized-menu-item__"][title="Trade"].time-complete {
            position: relative;
            background-size: cover;
            background-position: center;
            background-image: url('data:image/svg+xml;charset=UTF-8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><rect x="5" y="5" width="90" height="90" stroke="green" stroke-width="10" fill="none"/></svg>');
        }
    </style>`);

    const tradeChatButton = await waitFor("#chatRoot [class*='minimized-menu-item__'][title='Trade']");
    let tradeChat = tradeChatButton.className.includes("minimized-menu-item--open__") ? await getTradeChat() : null;

    const setTimer = () => {
        const timestamp = parseInt(localStorage.getItem(STORAGE_KEY) || Date.now());
        const timeLeft = Math.floor(60 - ((Date.now() - timestamp) / 1000));
        tradeChatButton.classList.toggle("time-left", timeLeft > 0);
        tradeChatButton.classList.toggle("time-complete", timeLeft <= 0);
        tradeChatButton.style.backgroundImage = timeLeft > 0
            ? `url('data:image/svg+xml;charset=UTF-8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><rect x="5" y="5" width="90" height="90" stroke="red" stroke-width="10" fill="none" stroke-dasharray="360" stroke-dashoffset="${360 * (1 - timeLeft / 60)}"/></svg>')`
            : `url('data:image/svg+xml;charset=UTF-8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><rect x="5" y="5" width="90" height="90" stroke="green" stroke-width="10" fill="none"/></svg>')`;
        if (!localStorage.getItem(STORAGE_KEY)) localStorage.setItem(STORAGE_KEY, Date.now());
    };

    const handleNewMessage = async (chat) => {
        const chatBody = chat.querySelector("[class*='chat-box-body___']");
        const message = await new Promise(resolve => {
            new MutationObserver((mutations, observer) => {
                const mutation = mutations.find(m => m.addedNodes.length);
                if (!mutation) return;
                observer.disconnect();
                resolve(mutation.addedNodes[0]);
            }).observe(chatBody, { childList: true });
        });
        if (!message.className.includes("chat-box-body__block-message-wrapper__") || message.textContent !== "Trade rooms allows one message per 60 seconds") {
            localStorage.setItem(STORAGE_KEY, Date.now());
        }
    };

    const attachKeyDownListener = (chat) => {
        chat.querySelector("textarea").addEventListener("keydown", async e => {
            if (e.key === "Enter" && Math.floor(60 - ((Date.now() - parseInt(localStorage.getItem(STORAGE_KEY))) / 1000)) <= 0) {
                await handleNewMessage(chat);
            }
        });
    };

    if (tradeChat) attachKeyDownListener(tradeChat);

    tradeChatButton.addEventListener("click", async () => {
        if (!tradeChatButton.className.includes("minimized-menu-item--open__")) {
            tradeChat = await getTradeChat();
        }
        if (tradeChat) attachKeyDownListener(tradeChat);
    });

    document.addEventListener("click", async e => {
        const specificButton = document.querySelector("button.chat-box-footer__send-icon-wrapper___fGx9E");
        if (specificButton && specificButton.contains(e.target) && Math.floor(60 - ((Date.now() - parseInt(localStorage.getItem(STORAGE_KEY))) / 1000)) <= 0) {
            await handleNewMessage(tradeChat);
        }
    });

    setTimer();
    setInterval(setTimer, 1000);

    async function getTradeChat() {
        await waitFor("#chatRoot [class*='chat-box-header__']");
        return [...document.querySelectorAll("#chatRoot [class*='chat-box-header__']")].find(x => x.textContent === "Trade")?.closest("[class*='chat-box__']");
    }
})();