Trade Chat Timer on Button

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

目前为 2024-06-01 提交的版本。查看 最新版本

// ==UserScript==
// @name         Trade Chat Timer on Button
// @namespace    http://tampermonkey.net/
// @version      1.5
// @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 checkExist = () => {
            const el = parent.querySelector(selector);
            if (el) {
                resolve(el);
            } else {
                requestAnimationFrame(checkExist);
            }
        };
        checkExist();
    });
}

(async () => {
    const addStyle = () => {
        if (!document.head.querySelector("#trade-chat-timer-style")) {
            const style = document.createElement('style');
            style.id = "trade-chat-timer-style";
            style.textContent = `
                #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>');
                }
            `;
            document.head.appendChild(style);
        }
    };

    addStyle();

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

    const updateTimerVisual = (timeLeft) => {
        if (timeLeft > 0) {
            tradeChatButton.classList.add("time-left");
            tradeChatButton.classList.remove("time-complete");
            tradeChatButton.style.backgroundImage = `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 / 60000)}"/></svg>')`;
        } else {
            tradeChatButton.classList.remove("time-left");
            tradeChatButton.classList.add("time-complete");
            tradeChatButton.style.backgroundImage = `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>')`;
        }
    };

    const setTimer = () => {
        const timestamp = parseInt(localStorage.getItem(STORAGE_KEY) || Date.now());
        const timeUntil = Math.max(60000 - (Date.now() - timestamp), 0);
        updateTimerVisual(timeUntil);
        if (!localStorage.getItem(STORAGE_KEY)) localStorage.setItem(STORAGE_KEY, Date.now());
    };

    const resetTimer = () => {
        const timestamp = Date.now();
        localStorage.setItem(STORAGE_KEY, timestamp);
        updateTimerVisual(60000);
    };

    const throttle = (func, limit) => {
        let lastFunc;
        let lastRan;
        return function(...args) {
            if (!lastRan) {
                func(...args);
                lastRan = Date.now();
            } else {
                clearTimeout(lastFunc);
                lastFunc = setTimeout(function() {
                    if ((Date.now() - lastRan) >= limit) {
                        func(...args);
                        lastRan = Date.now();
                    }
                }, limit - (Date.now() - lastRan));
            }
        };
    };

    async function checkForBlockMessage(chatBody) {
        const lastMessage = chatBody.lastElementChild;
        if (lastMessage && lastMessage.classList.contains("chat-box-body__block-message-wrapper___JjbKr") && lastMessage.textContent.includes("Trade chat allows one message per 60 seconds")) {
            return true;
        }
        return false;
    }

    async function handleNewMessage(chat) {
        const chatBody = chat.querySelector("[class*='chat-box-body___']");
        const message = await new Promise(resolve => {
            new MutationObserver((mutations, observer) => {
                const mutation = mutations.find(mutation => mutation.addedNodes.length);
                if (!mutation) return;

                const node = mutation.addedNodes[0];
                observer.disconnect();
                resolve(node);
            }).observe(chatBody, { childList: true });
        });

        if (await checkForBlockMessage(chatBody)) {
            return;
        }
        resetTimer();
    }

    const attachKeyUpListener = (chat) => {
        const textarea = chat.querySelector("textarea");
        if (textarea) {
            textarea.addEventListener("keyup", async e => {
                if (e.key === "Enter") {
                    await handleNewMessage(chat);
                }
            });
        }
    };

    const attachMutationObserver = (chat) => {
        const chatBody = chat.querySelector("[class*='chat-box-body___']");
        new MutationObserver(throttle(async (mutations) => {
            if (await checkForBlockMessage(chatBody)) {
                return;
            }
        }, 500)).observe(chatBody, { childList: true, subtree: true });
    };

    if (tradeChat) {
        attachKeyUpListener(tradeChat);
        attachMutationObserver(tradeChat);
    }

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

    document.addEventListener("click", async e => {
        const specificButton = document.querySelector("button.chat-box-footer__send-icon-wrapper___fGx9E");
        if (specificButton && specificButton.contains(e.target)) {
            await handleNewMessage(tradeChat);
        }
    });

    setTimer();
    setInterval(setTimer, 100);

    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__']");
    }
})();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址