您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Adds a smart context-sensitive UI to JanitorAI, featuring: advanced token filter, persistent auto-pagination, proxy/definition checker, context-dependent menu (full controls on main, compact emoji menu on chat), animated emoji for 'replying...', and toggles in the settings panel to show/hide proxy & definition icons or change opacity of cards.
// ==UserScript== // @name JanitorAI Improvements: Tokens+Proxy/Definition check+Custom emojis... // @namespace http://tampermonkey.net/ // @version 2025.09.20.3 // @author WolfgangNoir // @description Adds a smart context-sensitive UI to JanitorAI, featuring: advanced token filter, persistent auto-pagination, proxy/definition checker, context-dependent menu (full controls on main, compact emoji menu on chat), animated emoji for 'replying...', and toggles in the settings panel to show/hide proxy & definition icons or change opacity of cards. // @match https://janitorai.com/* // @grant GM_getValue // @grant GM_setValue // @icon https://files.catbox.moe/jer5m2.png // ==/UserScript== (function () { 'use strict'; if (location.pathname === '/my_characters') return; // -- PERSISTENT USER OPTION UTILS -- function getProxyCheckEnabled() { return typeof GM_getValue !== "undefined" ? GM_getValue("proxyCheckEnabled", true) : true; } function setProxyCheckEnabled(val) { if (typeof GM_setValue !== "undefined") GM_setValue("proxyCheckEnabled", !!val); } function getDefinitionCheckEnabled() { return typeof GM_getValue !== "undefined" ? GM_getValue("defCheckEnabled", true) : true; } function setDefinitionCheckEnabled(val) { if (typeof GM_setValue !== "undefined") GM_setValue("defCheckEnabled", !!val); } function getHideOnBadProxy() { return typeof GM_getValue !== "undefined" ? GM_getValue("hideOnBadProxy", false) : false; } function setHideOnBadProxy(val) { if (typeof GM_setValue !== "undefined") GM_setValue("hideOnBadProxy", !!val); } function getHideOnHiddenDef() { return typeof GM_getValue !== "undefined" ? GM_getValue("hideOnHiddenDef", false) : false; } function setHideOnHiddenDef(val) { if (typeof GM_setValue !== "undefined") GM_setValue("hideOnHiddenDef", !!val); } function getAnimatedEmoji() { return typeof GM_getValue !== "undefined" ? GM_getValue("animatedEmoji", "🦈") : "🦈"; } function setAnimatedEmoji(emoji) { if (typeof GM_setValue !== "undefined" && emoji.length > 0) GM_setValue("animatedEmoji", emoji); } function getEmojiMenuEnabled() { if (typeof GM_getValue !== "undefined") { return GM_getValue("emojiMenuEnabled", true); } return true; } function setEmojiMenuEnabled(val) { if (typeof GM_setValue !== "undefined") { GM_setValue("emojiMenuEnabled", !!val); } } // ------------- MINI-EMOJI-MENU EN CHAT ------------- function insertEmojiOnlyMenuToggleable() { if (!getEmojiMenuEnabled()) { const panel = document.getElementById('janitorai-emoji-only-ui'); if (panel) panel.remove(); const btn = document.getElementById('janitorai-emoji-toggle-btn'); if (btn) btn.remove(); return; } if (document.getElementById('janitorai-emoji-toggle-btn')) return; const toggleBtn = document.createElement('button'); toggleBtn.id = 'janitorai-emoji-toggle-btn'; toggleBtn.title = 'Show/Hide emoji selector'; toggleBtn.style.position = 'fixed'; toggleBtn.style.top = '75px'; toggleBtn.style.left = '10px'; toggleBtn.style.zIndex = '100001'; toggleBtn.style.width = '32px'; toggleBtn.style.height = '32px'; toggleBtn.style.fontSize = '22px'; toggleBtn.style.background = 'rgba(48,48,48,0.92)'; toggleBtn.style.border = 'none'; toggleBtn.style.borderRadius = '50%'; toggleBtn.style.display = 'flex'; toggleBtn.style.alignItems = 'center'; toggleBtn.style.justifyContent = 'center'; toggleBtn.style.cursor = 'pointer'; toggleBtn.style.boxShadow = '0 2px 8px #0007'; toggleBtn.textContent = '👀'; if (!document.getElementById('janitorai-emoji-only-ui')) { const container = document.createElement('div'); container.id = "janitorai-emoji-only-ui"; container.style.position = 'fixed'; container.style.top = '118px'; container.style.left = '10px'; container.style.zIndex = '99999'; container.style.background = 'rgba(40,40,60,0.98)'; container.style.padding = '14px 18px'; container.style.borderRadius = '14px'; container.style.fontFamily = 'sans-serif'; container.style.fontSize = '15px'; container.style.color = '#fff'; container.style.boxShadow = '0 2px 12px #111a'; container.style.display = 'none'; container.innerHTML = ` <div style="margin-bottom:3px;"><b>Custom emoji for "replying..."</b></div> <input id="janitorai-emoji-input" type="text" maxlength="2" value="${getAnimatedEmoji()}" style="width:38px; text-align:center; font-size:18px;"> <button id="janitorai-emoji-save" style="margin-left:5px;">Save emoji</button> <span style="font-size:12px;color:#aaa;">Will take effect instantly.</span> `; document.body.appendChild(container); document.getElementById('janitorai-emoji-save').onclick = function () { const em = document.getElementById('janitorai-emoji-input').value.trim(); if (em.length > 0) { setAnimatedEmoji(em); alert("Emoji saved: " + em); } else { alert("Please enter a valid emoji."); } }; } document.body.appendChild(toggleBtn); toggleBtn.onclick = function () { const panel = document.getElementById('janitorai-emoji-only-ui'); if (panel) { panel.style.display = (panel.style.display === 'none') ? '' : 'none'; } }; } // ---------- MENÚ AVANZADO, TOKEN, PROXY, DEF HIDE, ETC ---------- const TOKEN_FILTER_KEY = 'janitorAITokenFilter'; const MENU_VISIBLE_KEY = 'janitorAIMenuVisible'; const PAGINATION_KEY = 'janitorAIPaginationOn'; const DEFAULT_MIN_TOKENS = 500; let minTokens = parseInt(localStorage.getItem(TOKEN_FILTER_KEY), 10) || DEFAULT_MIN_TOKENS; let paginationEnabled = localStorage.getItem(PAGINATION_KEY) === 'true'; let controlPanel = null; function insertUI() { if (!document.body) return setTimeout(insertUI, 300); if (!document.getElementById('janitor-control-panel')) { controlPanel = document.createElement('div'); controlPanel.id = 'janitor-control-panel'; Object.assign(controlPanel.style, { position: 'fixed', top: '75px', left: '10px', zIndex: '100000', display: 'flex', flexDirection: 'column', gap: '5px', alignItems: 'flex-start' }); const settingsButton = document.createElement('button'); settingsButton.id = 'token-filter-toggle'; settingsButton.textContent = '🛠️'; Object.assign(settingsButton.style, { width: '30px', height: '30px', padding: '0', backgroundColor: 'rgba(74, 74, 74, 0.7)', color: '#fff', border: 'none', borderRadius: '5px', cursor: 'pointer', display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: '16px', transition: 'background-color 0.2s' }); settingsButton.title = 'Show/hide advanced menu'; settingsButton.onclick = function () { const menu = document.getElementById('janitorai-enhanced-ui'); if (menu) { menu.style.display = menu.style.display === 'none' ? '' : 'none'; localStorage.setItem(MENU_VISIBLE_KEY, menu.style.display !== 'none'); } }; controlPanel.appendChild(settingsButton); document.body.appendChild(controlPanel); } if (document.getElementById('janitorai-enhanced-ui')) return; const container = document.createElement('div'); container.id = "janitorai-enhanced-ui"; container.style.position = 'fixed'; container.style.top = '38px'; container.style.left = '50px'; container.style.zIndex = '99999'; container.style.background = 'rgba(40,40,60,0.98)'; container.style.padding = '14px 18px'; container.style.borderRadius = '14px'; container.style.fontFamily = 'sans-serif'; container.style.fontSize = '15px'; container.style.color = '#fff'; container.style.boxShadow = '0 2px 12px #111a'; container.style.display = localStorage.getItem(MENU_VISIBLE_KEY) === 'true' ? '' : 'none'; container.innerHTML = `<b>JanitorAI Improvements:</b><br/>` + `<label style="display:flex;align-items:center;margin-bottom:3px;"> <input type="checkbox" id="janitorai-proxy-toggle-checkbox" style="margin-right:6px;" ${getProxyCheckEnabled() ? "checked" : ""}> Enable proxy status icon </label>` + `<label style="display:flex;align-items:center;margin-bottom:10px;"> <input type="checkbox" id="janitorai-def-toggle-checkbox" style="margin-right:6px;" ${getDefinitionCheckEnabled() ? "checked" : ""}> Enable definition status icon </label>` + `<label style="display:flex;align-items:center;margin-bottom:3px;"> <input type="checkbox" id="janitorai-hide-proxy-checkbox" style="margin-right:6px;" ${getHideOnBadProxy() ? "checked" : ""}> Change opacity if proxy is not allowed </label>` + `<label style="display:flex;align-items:center;margin-bottom:10px;"> <input type="checkbox" id="janitorai-hide-def-checkbox" style="margin-right:6px;" ${getHideOnHiddenDef() ? "checked" : ""}> Change opacity if definition is hidden </label>` + `Min tokens: <input id="janitorai-token-input" type="number" value="${minTokens}" style="width:80px;"> <button id="janitorai-token-save">Filter</button> <hr style="margin:8px 0;"> <button id="janitorai-toggle-pagination">Auto-pagination: <span id="janitorai-pagination-state">${paginationEnabled ? 'ON' : 'OFF'}</span></button> <hr style="margin:8px 0;"> <label style="display:flex;align-items:center;margin-bottom:7px;"> <input type="checkbox" id="janitorai-emoji-toggle-checkbox" style="margin-right:6px;" ${getEmojiMenuEnabled() ? "checked" : ""}> Show emoji menu 👀 in chat page: </label> <div style="margin-bottom:3px;"><b>Custom emoji for "replying..."</b></div> <input id="janitorai-emoji-input" type="text" maxlength="2" value="${getAnimatedEmoji()}" style="width:38px; text-align:center; font-size:18px;"> <button id="janitorai-emoji-save" style="margin-left:5px;">Save emoji</button> <span style="font-size:12px;color:#aaa;">Will take effect instantly.</span> `; document.body.appendChild(container); document.getElementById('janitorai-proxy-toggle-checkbox').onchange = function(e) { setProxyCheckEnabled(e.target.checked); document.querySelectorAll('.proxy-status-icon').forEach(ic => ic.remove()); document.querySelectorAll('.profile-character-card-stack-link-component').forEach(card => card.removeAttribute('custom-icons-checked')); }; document.getElementById('janitorai-def-toggle-checkbox').onchange = function(e) { setDefinitionCheckEnabled(e.target.checked); document.querySelectorAll('.definition-status-icon').forEach(ic => ic.remove()); document.querySelectorAll('.profile-character-card-stack-link-component').forEach(card => card.removeAttribute('custom-icons-checked')); }; document.getElementById('janitorai-hide-proxy-checkbox').onchange = function(e) { setHideOnBadProxy(e.target.checked); document.querySelectorAll('.profile-character-card-stack-link-component').forEach(card => card.removeAttribute('custom-icons-checked')); }; document.getElementById('janitorai-hide-def-checkbox').onchange = function(e) { setHideOnHiddenDef(e.target.checked); document.querySelectorAll('.profile-character-card-stack-link-component').forEach(card => card.removeAttribute('custom-icons-checked')); }; document.getElementById('janitorai-token-save').onclick = function () { const value = parseInt(document.getElementById('janitorai-token-input').value, 10); minTokens = isNaN(value) ? DEFAULT_MIN_TOKENS : value; localStorage.setItem(TOKEN_FILTER_KEY, minTokens); filterCards(); }; document.getElementById('janitorai-toggle-pagination').onclick = function () { paginationEnabled = !paginationEnabled; document.getElementById('janitorai-pagination-state').innerText = paginationEnabled ? "ON" : "OFF"; localStorage.setItem(PAGINATION_KEY, paginationEnabled); }; document.getElementById('janitorai-emoji-save').onclick = function () { const em = document.getElementById('janitorai-emoji-input').value.trim(); if (em.length > 0) { setAnimatedEmoji(em); alert("Emoji saved: " + em); } else { alert("Please enter a valid emoji."); } }; document.getElementById('janitorai-emoji-toggle-checkbox').onchange = function (e) { setEmojiMenuEnabled(e.target.checked); if (location.pathname.startsWith('/chats')) location.reload(); }; } function parseTokens(cardElement) { const tokenSpan = cardElement.querySelector(".chakra-text.pp-cc-tokens-count.profile-character-card-tokens-count"); if (tokenSpan) { const raw = tokenSpan.textContent.replace(/\s+tokens?/i, '').trim(); let tokens; if (raw.endsWith('k')) { tokens = parseFloat(raw) * 1000; } else { tokens = parseInt(raw.replace(/\D/g, ''), 10); } return tokens; } return null; } function filterCards() { const cards = document.querySelectorAll('.pp-cc-wrapper.profile-character-card-wrapper'); cards.forEach(card => { const tokens = parseTokens(card); card.style.display = (tokens !== null && tokens >= minTokens) ? '' : 'none'; }); } // ---- AUTO PAGINACIÓN ---- (function() { let isNavigating = false; let scrollCount = 0; const requiredScrolls = 3; const pageDelay = 2000; function isAtVeryBottom() { const scrollPosition = window.scrollY + window.innerHeight; const pageHeight = document.documentElement.scrollHeight; return pageHeight - scrollPosition <= 1; } function getNextPageElement() { return document.querySelector('button[aria-label="Next Page"]:not([disabled]),button.profile-pagination-next-button:not([disabled])'); } window.addEventListener('wheel', function(event) { if (!paginationEnabled || isNavigating) return; if (event.deltaY > 0 && isAtVeryBottom()) { scrollCount++; if (scrollCount >= requiredScrolls) { const nextPage = getNextPageElement(); if (nextPage) { isNavigating = true; nextPage.click(); setTimeout(() => { isNavigating = false; scrollCount = 0; }, pageDelay); } } } else if (!isAtVeryBottom()) { scrollCount = 0; } }, { passive: true }); })(); const observerDom = new MutationObserver(() => { insertUI(); filterCards(); }); // == PROXY + DEFINITION STATUS ICONS + OPACITY BEHAVIOR == const proxyAllowedForKey = "paf_cache_"; async function proxyAllowedFor(characterURL) { const key = proxyAllowedForKey + characterURL; const cache = typeof GM_getValue === "function" ? GM_getValue(key) : null; if (cache) { const { allowed, timestamp } = JSON.parse(cache); if (Date.now() - timestamp < 86400000) { return { allowed: allowed, cached: true }; } } const response = await fetch(characterURL); const page = await response.text(); const allowed = page.includes("<div>proxy allowed</div>"); if(typeof GM_setValue === "function") { GM_setValue(key, JSON.stringify({ allowed: allowed, timestamp: Date.now() })); } return { allowed: allowed, cached: false }; } async function definitionIsHidden(characterURL) { try { const response = await fetch(characterURL); const html = await response.text(); const parser = new DOMParser(); const doc = parser.parseFromString(html, "text/html"); const elements = doc.querySelectorAll('h4, div, span'); for (let el of elements) { if (el.textContent && el.textContent.includes("Character Definition is hidden")) { return true; } } return false; } catch (e) { return false; } } setInterval(async function () { if (location.pathname.startsWith('/chats')) return; const hideOnBadProxy = getHideOnBadProxy(); const hideOnHiddenDef = getHideOnHiddenDef(); const showProxy = getProxyCheckEnabled(); const showDef = getDefinitionCheckEnabled(); const characterCardElements = [...document.querySelectorAll(".profile-character-card-stack-link-component")]; for (let i = 0; i < characterCardElements.length; i++) { const element = characterCardElements[i]; if (!document.body.contains(element)) continue; if (!element.getAttribute("custom-icons-checked")) { element.setAttribute("custom-icons-checked", "yes"); const cardWrapper = element.parentElement.parentElement; const titleElement = element.children[0] && element.children[0].children[0]; let proxyRes = { allowed: true }; let defRes = false; if (showProxy || hideOnBadProxy) proxyRes = await proxyAllowedFor(element.href); if (showDef || hideOnHiddenDef) defRes = await definitionIsHidden(element.href); let faded = false; if (hideOnBadProxy && !proxyRes.allowed) faded = true; if (hideOnHiddenDef && defRes) faded = true; if (cardWrapper) { cardWrapper.style.opacity = faded ? 0.25 : 1; cardWrapper.style.pointerEvents = faded ? "none" : ""; } ['proxy-status-icon','definition-status-icon'].forEach(cls => { const old = titleElement && titleElement.querySelector('.'+cls); if (old) old.remove(); }); if (showDef) { const defIcon = document.createElement('span'); defIcon.className = 'definition-status-icon'; defIcon.style.marginRight = "6px"; defIcon.style.fontSize = "1.2em"; defIcon.textContent = defRes ? "❌" : "✅"; defIcon.title = defRes ? "Definition is hidden" : "Definition visible"; if (titleElement && titleElement.firstChild) titleElement.insertBefore(defIcon, titleElement.firstChild); else if (titleElement) titleElement.appendChild(defIcon); } if (showProxy) { const proxyIcon = document.createElement('span'); proxyIcon.className = 'proxy-status-icon'; proxyIcon.style.marginRight = "6px"; proxyIcon.style.fontSize = "1.2em"; proxyIcon.textContent = proxyRes.allowed ? "🟢" : "🔴"; proxyIcon.title = proxyRes.allowed ? "Proxy OK" : "Proxy not allowed"; if (titleElement && titleElement.firstChild) titleElement.insertBefore(proxyIcon, titleElement.firstChild); else if (titleElement) titleElement.appendChild(proxyIcon); } } } }, 2500); // ----------- CONTROLADOR DE NAVEGACIÓN SPA ----------- let lastLocation = location.pathname; setInterval(() => { if (location.pathname !== lastLocation) { lastLocation = location.pathname; onPageChange(); } }, 400); function onPageChange() { document.getElementById('janitor-control-panel')?.remove(); document.getElementById('janitorai-enhanced-ui')?.remove(); document.getElementById('janitorai-emoji-toggle-btn')?.remove(); document.getElementById('janitorai-emoji-only-ui')?.remove(); if (location.pathname.startsWith('/chats')) { insertEmojiOnlyMenuToggleable(); } else { insertUI(); filterCards(); observerDom.observe(document.body, {childList: true, subtree: true}); } } // --------- EMOJI REPLYING OVERLAY --------- (() => { const ANIMATION_FRAMES = () => { const emoji = getAnimatedEmoji(); return [ emoji, emoji + emoji, emoji + emoji + emoji ]; }; const FRAME_INTERVAL = 500; const seenContainers = new WeakSet(); function createEmojiOverlay(p) { if (p.dataset.emojiOverlayAttached) return; let parent = p.parentElement; while (parent && getComputedStyle(parent).position === 'static') parent = parent.parentElement; if (!parent) return; if (getComputedStyle(parent).position === 'static') parent.style.position = 'relative'; const overlay = document.createElement('div'); overlay.style.position = 'absolute'; overlay.style.pointerEvents = 'none'; overlay.style.left = `${p.offsetLeft}px`; overlay.style.top = `${p.offsetTop}px`; overlay.style.font = getComputedStyle(p).font; overlay.style.color = getComputedStyle(p).color; overlay.style.zIndex = '9999'; overlay.style.whiteSpace = 'pre'; const shadow = overlay.attachShadow({ mode: 'open' }); const span = document.createElement('span'); shadow.appendChild(span); let frameIndex = 0; const interval = setInterval(() => { if (!document.body.contains(p)) { clearInterval(interval); overlay.remove(); return; } if (!/^replying/i.test(p.innerText.trim())) { clearInterval(interval); overlay.remove(); p.removeAttribute('data-emoji-overlay-attached'); p.style.opacity = ''; return; } span.textContent = ANIMATION_FRAMES()[frameIndex++ % ANIMATION_FRAMES().length]; }, FRAME_INTERVAL); parent.appendChild(overlay); p.dataset.emojiOverlayAttached = 'true'; p.style.opacity = '0'; } function handleParagraph(p) { if (/^replying\.*/i.test(p.innerText.trim())) { createEmojiOverlay(p); } } function observeContainer(container) { if (seenContainers.has(container)) return; seenContainers.add(container); const observer = new MutationObserver(mutations => { mutations.forEach(mutation => { mutation.addedNodes.forEach(node => { if (node.nodeType === 1) { if (node.tagName === 'P') { handleParagraph(node); } else { node.querySelectorAll?.('p')?.forEach(handleParagraph); } } }); if (mutation.type === 'characterData' && mutation.target.nodeType === 3) { const p = mutation.target.parentElement; if (p?.tagName === 'P') { handleParagraph(p); } } }); }); observer.observe(container, { childList: true, subtree: true, characterData: true, }); container.querySelectorAll('p').forEach(handleParagraph); } function scanAndObserve() { const containers = document.querySelectorAll('div.css-zpjg'); containers.forEach(observeContainer); } scanAndObserve(); setInterval(scanAndObserve, 500); })(); // Inicializador al cargar onPageChange(); (function forceTokenInputContrast() { const style = document.createElement('style'); style.innerHTML = ` #janitorai-token-input { background: #fff !important; color: #111 !important; border: 1px solid #aaa !important; } `; document.head.appendChild(style); })(); })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址