您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Show a timer that shows the time left to post next message.
当前为
// ==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或关注我们的公众号极客氢云获取最新地址