// ==UserScript== // @name Axiom Trade - Keyword Highlighter // @namespace [URL REMOVED] // @version 7.3.0 // @description Highlights whole-word keywords only inside Axiom's tweet/profile popups // @author You // @match [URL REMOVED] // @grant GM_setValue // @grant GM_getValue // @run-at document-idle // ==/UserScript== (function () { 'use strict'; const STORAGE_KEY = 'axiom_highlight_keywords'; const POS_KEY = 'axiom_panel_position'; const SETTINGS_ID = 'axiom-kw-settings'; const HOST_ID = 'axiom-kw-host'; const HIGHLIGHT_CLR = '#FFE033'; const HIGHLIGHT_BG = '#2a2000'; const MARK_ATTR = 'data-axiom-kw'; // ─── Storage ────────────────────────────────────────────────────────────────── function getKeywords() { try { return JSON.parse(GM_getValue(STORAGE_KEY, '[]')); } catch { return []; } } function saveKeywords(list) { GM_setValue(STORAGE_KEY, JSON.stringify(list)); } function getSavedPos() { try { return JSON.parse(GM_getValue(POS_KEY, 'null')); } catch { return null; } } function savePos(x, y) { GM_setValue(POS_KEY, JSON.stringify({ x, y })); } // ─── Pattern (whole-word, case-insensitive) ─────────────────────────────────── function escapeRegex(s) { return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); } function buildPattern(keywords) { const terms = keywords.map(k => k.trim()).filter(Boolean).map(escapeRegex); return terms.length ? new RegExp(`\\b(${terms.join('|')})\\b`, 'gi') : null; } // ─── DOM Highlighting ───────────────────────────────────────────────────────── const SKIP_TAGS = new Set(['SCRIPT','STYLE','NOSCRIPT','TEXTAREA','INPUT','SELECT','MARK']); function highlightInContainer(container, pattern) { const walker = document.createTreeWalker(container, NodeFilter.SHOW_TEXT, { acceptNode(node) { const p = node.parentElement; if (!p || SKIP_TAGS.has(p.tagName)) return NodeFilter.FILTER_REJECT; if (p.closest(`[${MARK_ATTR}]`)) return NodeFilter.FILTER_REJECT; if (!node.textContent.trim()) return NodeFilter.FILTER_REJECT; return NodeFilter.FILTER_ACCEPT; } }); const nodes = []; let n; while ((n = walker.nextNode())) nodes.push(n); for (const tn of nodes) { const text = tn.textContent; pattern.lastIndex = 0; if (!pattern.test(text)) continue; pattern.lastIndex = 0; const frag = document.createDocumentFragment(); let last = 0, m; while ((m = pattern.exec(text)) !== null) { if (m.index > last) frag.appendChild(document.createTextNode(text.slice(last, m.index))); const mark = document.createElement('mark'); mark.setAttribute(MARK_ATTR, '1'); mark.style.cssText = `background:${HIGHLIGHT_CLR}!important;color:#1a1400!important;` + `padding:0 2px;border-radius:2px;font-weight:700;font-style:normal;text-decoration:none;`; mark.textContent = m[0]; frag.appendChild(mark); last = pattern.lastIndex; } if (last < text.length) frag.appendChild(document.createTextNode(text.slice(last))); pattern.lastIndex = 0; if (tn.parentNode) tn.parentNode.replaceChild(frag, tn); } } // ─── Popup Detection ────────────────────────────────────────────────────────── function isTweetPopup(el) { if (!el || !el.querySelector) return false; const cs = window.getComputedStyle(el); if (cs.position !== 'fixed' && cs.position !== 'absolute') return false; if (cs.display === 'none' || cs.visibility === 'hidden') return false; const text = el.textContent || ''; if (text.trim().length < 10) return false; if (/read more on (x|twitter)/i.test(text)) return true; if (/joined\s+(jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec)\w*\s+\d{4}/i.test(text)) return true; if (/[\d,.]+\s*(k|m|b)?\s+followers?/i.test(text)) return true; return false; } function alreadyHighlighted(el) { return !!el.querySelector(`[${MARK_ATTR}]`); } function isOwnUI(el) { return el.id === HOST_ID || el.id === SETTINGS_ID || !!el.closest?.('#' + HOST_ID); } // ─── Process a candidate element ───────────────────────────────────────────── function processPopup(el) { if (!el || el.nodeType !== 1) return false; if (isOwnUI(el)) return false; if (!isTweetPopup(el)) return false; if (alreadyHighlighted(el)) return false; const kws = getKeywords(); if (!kws.length) return false; const pattern = buildPattern(kws); if (!pattern) return false; highlightInContainer(el, pattern); return true; } // ─── Cursor-targeted scan ───────────────────────────────────────────────────── let cursorX = 0, cursorY = 0; document.addEventListener('mousemove', e => { cursorX = e.clientX; cursorY = e.clientY; }, true); let scanInterval = null; let hoverWindowOpen = false; let hoverTimer = null; function startCursorScan() { stopCursorScan(); let ticks = 0; scanInterval = setInterval(() => { ticks++; if (ticks > 30 || !hoverWindowOpen) { stopCursorScan(); return; } const pts = [ [cursorX, cursorY], [cursorX, cursorY + 120], [cursorX, cursorY + 250], [cursorX - 150, cursorY + 120], [cursorX + 150, cursorY + 120], [cursorX, cursorY - 120], ]; const seen = new Set(); for (const [x, y] of pts) { if (x < 0 || y < 0 || x > window.innerWidth || y > window.innerHeight) continue; for (const el of document.elementsFromPoint(x, y)) { if (seen.has(el)) continue; seen.add(el); if (processPopup(el)) { stopCursorScan(); return; } } } }, 100); } function stopCursorScan() { if (scanInterval) { clearInterval(scanInterval); scanInterval = null; } } // ─── Social Icon Hover Detection ────────────────────────────────────────────── function isTwitterHref(href) { return /https?:\/\/(www\.)?(twitter|x)\.com\//.test(href || ''); } function findSocialAnchor(target) { let node = target; for (let i = 0; i < 8; i++) { if (!node) break; if (node.tagName === 'A' && isTwitterHref(node.href)) return node; if (node.querySelector) { const a = node.querySelector('a[href*="twitter.com/"],a[href*="x.com/"]'); if (a) return a; } node = node.parentElement; } return null; } document.addEventListener('mouseover', e => { if (!findSocialAnchor(e.target)) return; hoverWindowOpen = true; clearTimeout(hoverTimer); hoverTimer = setTimeout(() => { hoverWindowOpen = false; stopCursorScan(); }, 3000); startCursorScan(); }, true); // ─── MutationObserver (handles React portal popups added to DOM) ────────────── new MutationObserver(mutations => { if (!hoverWindowOpen) return; for (const mut of mutations) { if (mut.type !== 'childList') continue; for (const node of mut.addedNodes) { if (node.nodeType !== 1) continue; if (processPopup(node)) continue; for (const child of (node.children || [])) { if (processPopup(child)) break; for (const gc of (child.children || [])) { if (processPopup(gc)) break; } } } } }).observe(document.documentElement, { childList: true, subtree: true }); // ─── Drag helper ───────────────────────────────────────────────────────────── // // Attaches to a handle element and moves the host around the screen. // On first drag it switches the host from centred (left:50%+transform) // to absolute pixel coordinates so it tracks the cursor exactly. // Position is saved and restored across page loads. function makeDraggable(handle, host) { let dragging = false; let startMouseX, startMouseY, startElX, startElY; function applyPos(x, y) { // Clamp inside viewport const W = window.innerWidth, H = window.innerHeight; const w = host.offsetWidth || 300; const h = host.offsetHeight || 50; x = Math.max(0, Math.min(x, W - w)); y = Math.max(0, Math.min(y, H - h)); host.style.left = x + 'px'; host.style.top = y + 'px'; host.style.transform = 'none'; } function getHostPos() { const r = host.getBoundingClientRect(); return { x: r.left, y: r.top }; } handle.addEventListener('mousedown', e => { // Only drag on left-button on the handle itself (not on buttons/inputs inside) if (e.button !== 0) return; if (e.target.closest('button,input,a,[id="axiom-kw-close"]')) return; e.preventDefault(); dragging = true; const pos = getHostPos(); startElX = pos.x; startElY = pos.y; startMouseX = e.clientX; startMouseY = e.clientY; handle.style.cursor = 'grabbing'; }); document.addEventListener('mousemove', e => { if (!dragging) return; const dx = e.clientX - startMouseX; const dy = e.clientY - startMouseY; applyPos(startElX + dx, startElY + dy); }, true); document.addEventListener('mouseup', e => { if (!dragging) return; dragging = false; handle.style.cursor = 'grab'; const pos = getHostPos(); savePos(pos.x, pos.y); }, true); } // ─── Settings Panel ─────────────────────────────────────────────────────────── let panelHost = null; function ensurePanelHost() { if (panelHost && document.documentElement.contains(panelHost)) return panelHost; panelHost = document.createElement('div'); panelHost.id = HOST_ID; const saved = getSavedPos(); if (saved) { panelHost.style.cssText = `all:unset;position:fixed;top:${saved.y}px;left:${saved.x}px;transform:none;` + 'z-index:2147483647;pointer-events:all;' + 'font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;'; } else { panelHost.style.cssText = 'all:unset;position:fixed;top:10px;left:50%;transform:translateX(-50%);' + 'z-index:2147483647;pointer-events:all;' + 'font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;'; } document.documentElement.appendChild(panelHost); return panelHost; } function createSettingsPanel() { const host = ensurePanelHost(); if (host.querySelector('#' + SETTINGS_ID)) return; // ── Collapsed pill: small square with bold K ────────────────────────────── const pill = document.createElement('div'); pill.id = 'axiom-kw-pill'; pill.title = 'Keyword Highlighter — drag to move'; pill.style.cssText = 'display:none;width:34px;height:34px;border-radius:8px;' + 'background:#181818;border:1px solid #2a2a2a;cursor:grab;' + 'box-shadow:0 4px 16px rgba(0,0,0,0.8);' + 'align-items:center;justify-content:center;'; pill.innerHTML = ``; // ── Full panel ──────────────────────────────────────────────────────────── const panel = document.createElement('div'); panel.id = SETTINGS_ID; panel.style.cssText = 'background:#0f0f0f;border:1px solid #2a2a2a;border-radius:12px;' + 'box-shadow:0 8px 32px rgba(0,0,0,0.9);font-size:13px;color:#e0e0e0;width:284px;overflow:hidden;'; panel.innerHTML = `
⇅ 🔑 Keyword Highlighter ×
DISCORD.GG/CHAINED
`; host.appendChild(pill); host.appendChild(panel); const header = panel.querySelector('#axiom-kw-header'); const closeBtn = panel.querySelector('#axiom-kw-close'); const input = panel.querySelector('#axiom-kw-input'); const addBtn = panel.querySelector('#axiom-kw-add'); const list = panel.querySelector('#axiom-kw-list'); // ── Drag: header drags when expanded, pill drags when collapsed ─────────── makeDraggable(header, host); makeDraggable(pill, host); // ── Collapse / expand ───────────────────────────────────────────────────── function collapse() { panel.style.display = 'none'; pill.style.display = 'flex'; } function expand() { pill.style.display = 'none'; panel.style.display = 'block'; } closeBtn.addEventListener('click', e => { e.stopPropagation(); collapse(); }); // Expand only on click (not at end of drag) let pillDragMoved = false; pill.addEventListener('mousedown', () => { pillDragMoved = false; }); pill.addEventListener('mousemove', () => { pillDragMoved = true; }); pill.addEventListener('mouseup', () => { if (!pillDragMoved) expand(); }); // ── Keyword rendering ───────────────────────────────────────────────────── function renderKeywords() { const kws = getKeywords(); list.innerHTML = ''; if (!kws.length) { const e = document.createElement('span'); e.style.cssText = 'color:#383838;font-size:12px;'; e.textContent = 'No keywords yet'; list.appendChild(e); return; } kws.forEach((kw, i) => { const tag = document.createElement('span'); tag.style.cssText = `display:inline-flex;align-items:center;gap:4px;background:${HIGHLIGHT_BG};` + `color:${HIGHLIGHT_CLR};border-radius:6px;padding:3px 7px 3px 10px;` + `font-size:12px;font-weight:700;border:1px solid #3a3000;max-width:200px;`; const label = document.createElement('span'); label.style.cssText = 'overflow:hidden;text-overflow:ellipsis;white-space:nowrap;'; label.textContent = kw; const del = document.createElement('span'); del.style.cssText = 'cursor:pointer;color:#ff6b6b;font-size:16px;line-height:1;flex-shrink:0;'; del.textContent = '×'; del.title = 'Remove'; del.addEventListener('click', ev => { ev.stopPropagation(); const updated = getKeywords(); updated.splice(i, 1); saveKeywords(updated); renderKeywords(); }); tag.appendChild(label); tag.appendChild(del); list.appendChild(tag); }); } function addKeyword() { const val = input.value.trim(); if (!val) return; const kws = getKeywords(); if (!kws.map(k => k.toLowerCase()).includes(val.toLowerCase())) { kws.push(val); saveKeywords(kws); } input.value = ''; renderKeywords(); } addBtn.addEventListener('click', addKeyword); input.addEventListener('keydown', e => { if (e.key === 'Enter') addKeyword(); }); input.addEventListener('focus', () => { input.style.borderColor = '#555'; }); input.addEventListener('blur', () => { input.style.borderColor = '#2e2e2e'; }); addBtn.addEventListener('mouseover', () => { addBtn.style.background = '#2a5500'; }); addBtn.addEventListener('mouseout', () => { addBtn.style.background = '#1f4200'; }); pill.addEventListener('mouseover', () => { if (pill.style.display !== 'none') pill.style.background = '#222'; }); pill.addEventListener('mouseout', () => { pill.style.background = '#181818'; }); renderKeywords(); } // ─── Panel Guardian ─────────────────────────────────────────────────────────── createSettingsPanel(); setInterval(() => { const host = document.getElementById(HOST_ID); if (!host || !document.documentElement.contains(host) || !host.querySelector('#' + SETTINGS_ID)) { panelHost = null; createSettingsPanel(); } }, 1000); })();