// ==UserScript== // @name Trade Helper // @namespace http://tampermonkey.net/ // @version 2026-02-03 // @description Help you trade while working! // @author Cadis Etrama Di Raizel // @match https://farmrpg.com/index.php // @icon https://www.google.com/s2/favicons?sz=64&domain=farmrpg.com // @grant none // ==/UserScript== "use strict"; /* ============================ * Username Service (Global State) * ============================ */ class UsernameService { static KEY = "username"; static get() { const raw = localStorage.getItem(this.KEY); return raw ? JSON.parse(raw) : null; } static set(value) { localStorage.setItem(this.KEY, JSON.stringify(value)); } static reset() { localStorage.removeItem(this.KEY); } static sanitizeBasic(name) { name = name.trim(); if (name.startsWith("@")) name = name.slice(1); if (name.endsWith(":")) name = name.slice(0, -1); return name; } static sanitizeWithSpaces(name) { return UsernameService.sanitizeBasic(name).replaceAll("+", " "); } } /* ============================ * Chat Observer (Shared) * ============================ */ class ChatObserver { #observer = null; start(onMessage) { if (this.#observer) return; const container = document.getElementById("chatzoneDesktop") || document.getElementById("chatzoneMobile"); if (!container) { console.log("Chat container not found yet..."); return; } console.log("Chat container found, binding observer..."); this.#observer = new MutationObserver((mutations) => { for (const m of mutations) { for (const node of m.addedNodes) { if (node.classList && node.classList.contains("chat-txt")) { onMessage(node); } } } }); this.#observer.observe(container, { childList: true, subtree: true }); } stop() { if (!this.#observer) return; this.#observer.disconnect(); this.#observer = null; } } /* ============================ * Base Feature (Abstract) * ============================ */ class ChatFeature { #running = false; #observer = new ChatObserver(); #username = ""; start(promptText, sanitizer) { if (this.#running) return; let input = UsernameService.get(); if (!input) { input = prompt(promptText, this.#username); if (!input) return; UsernameService.set(input); } this.#username = sanitizer(input); this.#running = true; this.#observer.start((node) => this.onMessage(node)); this.onStart(this.#username); } stop() { if (!this.#running) return; this.#observer.stop(); this.#running = false; this.onStop(); } isRunning() { return this.#running; } get username() { return this.#username; } // Hooks onStart() {} onStop() {} onMessage() {} } /* ============================ * Mention Watcher Feature * ============================ */ class MentionFeature extends ChatFeature { #seen = []; #MAX_HISTORY = 200; constructor() { super(); if (Notification.permission === "default") { Notification.requestPermission(); } if (Notification.permission !== "granted") { console.warn("Notifications not permitted"); } } #fingerprint(node) { return node.textContent.trim(); } #notify(message) { new Notification("Mentioned", { body: message }); } onStart(username) { console.log("Starting Mention Watcher for username:", username); } onStop() { console.log("Mention Watcher stopped"); } onMessage(msgDiv) { if (!(msgDiv instanceof HTMLElement)) return; const textContainer = msgDiv.children["5"]; const text = textContainer?.innerText; if (!text) return; const fp = this.#fingerprint(msgDiv); if (text.includes(this.username) && !this.#seen.includes(fp)) { if (this.#seen.length > this.#MAX_HISTORY) this.#seen.shift(); this.#seen.push(fp); this.#notify(text); console.log("Mentioned:", text); } } start() { super.start("Paste your username", UsernameService.sanitizeBasic); } } /* ============================ * Border Highlight Feature * ============================ */ class BorderHighlightFeature extends ChatFeature { #border; constructor(border) { super(); this.#border = border; } onStart(username) { console.log("Starting Highlighter for username:", username); } onStop() { console.log("Highlighter stopped"); } onMessage(msgDiv) { if (!(msgDiv instanceof HTMLElement)) return; const link = msgDiv.querySelector("a[href^='profile.php?user_name=']"); if (!link) return; const raw = link.getAttribute("href").split("user_name=")[1]; const sanitized = UsernameService.sanitizeWithSpaces(raw); if (sanitized === this.username) { msgDiv.style.border = this.#border; msgDiv.style.borderRadius = "6px"; console.log("Own message highlighted:", msgDiv); } } start() { super.start("Paste your username", UsernameService.sanitizeWithSpaces); } } /* ============================ * UI Utilities * ============================ */ class ControlButton { static create() { const button = document.createElement("button"); Object.assign(button.style, { padding: "10px 16px", fontSize: "14px", lineHeight: "1", border: "none", cursor: "pointer", color: "#fff", backgroundColor: "#2563eb", whiteSpace: "nowrap", boxShadow: "0 2px 6px rgba(0,0,0,0.2)", width: "100%", marginBlock: "5px", }); return button; } } /* ============================ * Bootstrap * ============================ */ (function bootstrap() { const list = document.querySelector(".page-content > div > ul"); if (!list) return; const features = []; function mountFeature(feature, startLabel, stopLabel) { features.push(feature); const button = ControlButton.create(); const update = () => { if (feature.isRunning()) { button.textContent = stopLabel; button.style.backgroundColor = "#dc2626"; } else { button.textContent = startLabel; button.style.backgroundColor = "#2563eb"; } }; button.addEventListener("click", () => { feature.isRunning() ? feature.stop() : feature.start(); update(); }); const li = document.createElement("li"); li.appendChild(button); list.appendChild(li); update(); } // Features mountFeature( new MentionFeature(), "Start Mention Watcher", "Stop Mention Watcher", ); mountFeature( new BorderHighlightFeature("5px solid red"), "Spot Me", "Un-Spot Me", ); // Global Reset Button const resetAll = ControlButton.create(); resetAll.textContent = "Reset Username"; resetAll.style.backgroundColor = "#7c2d12"; resetAll.addEventListener("click", () => { UsernameService.reset(); features.forEach((f) => f.stop()); console.log("Username reset globally. All features stopped."); }); const li = document.createElement("li"); li.appendChild(resetAll); list.appendChild(li); })();