// ==UserScript== // @name 【SillyTavern / ST酒馆】html代码注入器-改 // @name:zh 【ST酒馆】html代码注入器-改 // @name:zh-CN 【ST酒馆】html代码注入器-改 // @name:zh-TW 【ST酒館】html程式碼注入器-改 // @name:ja 【SillyTavern】 HTMLコードインジェクター-改 // @name:ko 【SillyTavern】 HTML코드 삽입기-수정 // @name:en 【SillyTavern】 HTML Code Injector - Modified // @name:fr 【SillyTavern】 Injecteur de code HTML - Modifié // @name:de 【SillyTavern】 HTML-Code-Injektor - Modifiziert // @namespace https://greasyfork.org/users/590339-miaotouy // @version 1.1.6.1 // @description 可以让ST酒馆独立运行html代码 (Inject HTML code into SillyTavern pages.) // @description:zh 可以让ST酒馆独立运行html代码 // @description:zh-CN 可以让ST酒馆独立运行html代码 // @description:zh-TW 讓SillyTavern獨立運行html程式碼 // @description:ja SillyTavernでhtmlコードを独立して実行できるようにします // @description:ko SillyTavern에서 HTML 코드를 독립적으로 실행할 수 있습니다. // @description:en Inject HTML code into SillyTavern pages. // @description:fr Permet d'exécuter du code HTML de manière indépendante dans SillyTavern. // @description:de Ermöglicht die unabhängige Ausführung von HTML-Code in SillyTavern. // @author Qianzhuo // @match *://localhost:8000/* // @match *://127.0.0.1:8000/* // @match *://192.168.*.*:*/* // @match *://*/*:8000/* // @match *://frp-kit.top:*/* // @include /^https?:\/\/.*:8000\// // @grant GM_setValue // @grant GM_getValue // @require https://code.jquery.com/jquery-3.6.0.min.js // @license CC BY-NC 4.0 // @downloadURL none // ==/UserScript== /* 原作者:Qianzhuo 修改者:miaotouy 【SillyTavern / ST酒馆】html代码注入器 © 2024 by Qianzhuo is licensed under CC BY-NC 4.0. To view a copy of this license, visit https://creativecommons.org/licenses/by-nc/4.0/ */ (function () { 'use strict'; // ---------------------------------------- 全局变量 ---------------------------------------- let isInjectionEnabled, displayMode, lastMesTextContent, activationMode, customStartFloor, customEndFloor, savedPosition, isEdgeControlsCollapsed; let edgeControls, settingsPanel; // ---------------------------------------- 初始化函数 ---------------------------------------- function initScript() { if (!document.title.includes('SillyTavern')) { console.log('页面标题不是 "SillyTavern",脚本未运行。'); return; } initVariables(); createUI(); addEventListeners(); addDragFunctionality(); startObservers(); console.log('HTML注入器脚本已初始化'); } function initVariables() { isInjectionEnabled = false; displayMode = parseInt(GM_getValue('displayMode', 1)); lastMesTextContent = ''; activationMode = GM_getValue('activationMode', 'all'); customStartFloor = GM_getValue('customStartFloor', 1); customEndFloor = GM_getValue('customEndFloor', -1); savedPosition = GM_getValue('edgeControlsPosition', 'top-right'); isEdgeControlsCollapsed = GM_getValue('isEdgeControlsCollapsed', true); } // ---------------------------------------- UI 创建函数 ---------------------------------------- function createUI() { createSettingsPanel(); createEdgeControls(); addStyles(); } function createSettingsPanel() { settingsPanel = document.createElement('div'); settingsPanel.id = 'html-injector-settings'; settingsPanel.classList.add('drawer'); settingsPanel.style.display = 'none'; settingsPanel.innerHTML = `
HTML注入器设置

边缘控制面板位置

显示模式

激活楼层

`; document.body.appendChild(settingsPanel); } function createEdgeControls() { edgeControls = document.createElement('div'); edgeControls.id = 'html-injector-edge-controls'; edgeControls.innerHTML = `
`; document.body.appendChild(edgeControls); // 创建拖拽点 const dragDots = edgeControls.querySelector('.drag-dots'); for (let i = 0; i < 3; i++) { const column = document.createElement('div'); column.style.display = 'flex'; column.style.flexDirection = 'column'; column.style.justifyContent = 'space-between'; column.style.height = '15px'; // 调整高度以适应三个点 for (let j = 0; j < 2; j++) { const dot = document.createElement('div'); dot.style.width = '4px'; dot.style.height = '4px'; dot.style.borderRadius = '50%'; dot.style.backgroundColor = 'var(--smart-theme-body-color)'; column.appendChild(dot); } dragDots.appendChild(column); } const toggleEdgeControlsButton = document.createElement('button'); toggleEdgeControlsButton.id = 'toggle-edge-controls'; toggleEdgeControlsButton.textContent = '>>'; toggleEdgeControlsButton.style.cssText = ` position: absolute; left: -20px; top: 50%; transform: translateY(-50%); background-color: var(--SmartThemeBlurTintColor, rgba(22, 11, 18, 0.73)); color: var(--SmartThemeBodyColor, rgba(220, 220, 210, 1)); border: 1px solid var(--SmartThemeBorderColor, rgba(217, 90, 157, 0.5)); border-radius: 5px 0 0 5px; cursor: pointer; padding: 5px; user-select: none; font-size: 12px; height: 60px; `; edgeControls.appendChild(toggleEdgeControlsButton); updateEdgeControlsPosition(savedPosition); updateEdgeControlsDisplay(); } // ---------------------------------------- 事件监听器 ---------------------------------------- function addEventListeners() { addSettingsPanelListeners(); addEdgeControlsListeners(); addGlobalListeners(); document.getElementsByName('display-mode').forEach(radio => { radio.addEventListener('change', handleDisplayModeChange); }); } function addSettingsPanelListeners() { document.getElementById('activation-mode').addEventListener('change', handleActivationModeChange); document.getElementById('custom-start-floor').addEventListener('change', handleCustomStartFloorChange); document.getElementById('custom-end-floor').addEventListener('change', handleCustomEndFloorChange); document.getElementById('last-n-floors').addEventListener('change', handleLastNFloorsChange); document.getElementsByName('display-mode').forEach(radio => { radio.addEventListener('change', handleDisplayModeChange); }); document.getElementById('html-injector-close-settings').addEventListener('click', toggleSettingsPanel); } function addEdgeControlsListeners() { document.getElementById('edge-injection-toggle').addEventListener('change', handleToggleChange); document.getElementById('html-injector-toggle-panel').addEventListener('click', toggleSettingsPanel); document.getElementById('toggle-edge-controls').addEventListener('click', toggleEdgeControls); addDragFunctionality(); } function addGlobalListeners() { window.addEventListener('message', handleMessage); window.addEventListener('resize', handleResize); window.matchMedia('(prefers-color-scheme: dark)').addListener(updateAllIframesTheme); } function addDragFunctionality() { const dragHandle = document.getElementById('html-injector-drag-handle'); const edgeControls = document.getElementById('html-injector-edge-controls'); let isDragging = false; let startY, startTop; function handleDragStart(e) { isDragging = true; startY = e.type.includes('mouse') ? e.clientY : e.touches[0].clientY; startTop = edgeControls.offsetTop; e.preventDefault(); } function handleDragMove(e) { if (!isDragging) return; const clientY = e.type.includes('mouse') ? e.clientY : e.touches[0].clientY; let newTop = startTop + (clientY - startY); newTop = Math.max(0, Math.min(newTop, window.innerHeight - edgeControls.offsetHeight)); edgeControls.style.top = newTop + 'px'; } function handleDragEnd() { isDragging = false; } dragHandle.addEventListener('mousedown', handleDragStart); dragHandle.addEventListener('touchstart', handleDragStart); document.addEventListener('mousemove', handleDragMove); document.addEventListener('touchmove', handleDragMove); document.addEventListener('mouseup', handleDragEnd); document.addEventListener('touchend', handleDragEnd); } // ---------------------------------------- 观察器和定时器 ---------------------------------------- function startObservers() { const observer = new MutationObserver(handleDOMMutations); observer.observe(document.body, { childList: true, subtree: true }); setInterval(checkLastMesTextChange, 2000); } // ---------------------------------------- 核心功能函数 ---------------------------------------- function injectHtmlCode(specificMesText = null) { try { let mesTextElements = specificMesText ? [specificMesText] : Array.from(document.getElementsByClassName('mes_text')); let targetElements; switch (activationMode) { case 'first': targetElements = mesTextElements.slice(0, 1); break; case 'last': targetElements = mesTextElements.slice(-1); break; case 'lastN': targetElements = mesTextElements.slice(-customEndFloor); break; case 'custom': { const start = customStartFloor - 1; const end = customEndFloor === -1 ? undefined : customEndFloor; targetElements = mesTextElements.slice(start, end); break; } default: // 'all' targetElements = mesTextElements; } for (const mesText of targetElements) { const codeElements = mesText.getElementsByTagName('code'); for (const codeElement of codeElements) { let htmlContent = codeElement.innerText.trim(); if (htmlContent.startsWith('<') && htmlContent.endsWith('>')) { const iframe = document.createElement('iframe'); iframe.style.width = '100%'; iframe.style.border = 'none'; iframe.style.marginTop = '10px'; iframe.srcdoc = `
${htmlContent}
`; if (displayMode === 2) { const details = document.createElement('details'); const summary = document.createElement('summary'); summary.textContent = '[原代码]'; details.appendChild(summary); codeElement.parentNode.insertBefore(details, codeElement); details.appendChild(codeElement); } else if (displayMode === 3) { codeElement.style.display = 'none'; } codeElement.parentNode.insertBefore(iframe, codeElement.nextSibling); iframe.onload = function () { adjustIframeHeight(iframe); setTimeout(() => adjustIframeHeight(iframe), 500); }; if (iframe.contentWindow) { const resizeObserver = new ResizeObserver(() => adjustIframeHeight(iframe)); resizeObserver.observe(iframe.contentWindow.document.body); } } } } } catch (error) { console.error('HTML注入失败:', error); } } function removeInjectedIframes() { const iframes = document.querySelectorAll('.mes_text iframe'); iframes.forEach(iframe => iframe.remove()); const codeElements = document.querySelectorAll('.mes_text code'); codeElements.forEach(code => { code.style.display = ''; const details = code.closest('details'); if (details) { details.parentNode.insertBefore(code, details); details.remove(); } }); } // ---------------------------------------- 辅助函数 ---------------------------------------- function adjustIframeHeight(iframe) { try { if (iframe.contentWindow.document.body) { const height = iframe.contentWindow.document.documentElement.scrollHeight; iframe.style.height = (height + 5) + 'px'; } } catch (error) { console.error('调整iframe高度失败:', error); } } function getSystemTheme() { return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'; } function updateAllIframesTheme() { const iframes = document.querySelectorAll('.mes_text iframe'); iframes.forEach(iframe => { try { if (iframe.contentWindow) { iframe.contentWindow.postMessage({ type: 'themeChange', theme: getSystemTheme() }, '*'); } } catch (error) { console.error('更新iframe主题失败:', error); } }); } function updateEdgeControlsPosition(position) { if (!edgeControls) return; switch (position) { case 'top-right': edgeControls.style.top = '20vh'; edgeControls.style.transform = 'none'; break; case 'right-three-quarters': edgeControls.style.top = '75vh'; edgeControls.style.transform = 'none'; break; case 'right-middle': edgeControls.style.top = '50%'; edgeControls.style.transform = 'translateY(-50%)'; break; } edgeControls.style.right = isEdgeControlsCollapsed ? '-100px' : '0'; GM_setValue('edgeControlsPosition', position); } function updateEdgeControlsDisplay() { if (!edgeControls) return; edgeControls.style.right = isEdgeControlsCollapsed ? '-100px' : '0'; const toggleButton = document.getElementById('toggle-edge-controls'); if (toggleButton) { toggleButton.textContent = isEdgeControlsCollapsed ? '<<' : '>>'; } } function toggleSettingsPanel() { const isVisible = settingsPanel.style.display === 'block'; settingsPanel.style.display = isVisible ? 'none' : 'block'; document.getElementById('html-injector-toggle-panel').textContent = isVisible ? '显示面板' : '隐藏面板'; } function toggleEdgeControls() { isEdgeControlsCollapsed = !isEdgeControlsCollapsed; GM_setValue('isEdgeControlsCollapsed', isEdgeControlsCollapsed); updateEdgeControlsDisplay(); } function handleResize() { updateEdgeControlsPosition(savedPosition); } function handleActivationModeChange() { const customSettings = document.getElementById('custom-floor-settings'); const lastNSettings = document.getElementById('last-n-settings'); customSettings.style.display = this.value === 'custom' ? 'block' : 'none'; lastNSettings.style.display = this.value === 'lastN' ? 'block' : 'none'; activationMode = this.value; GM_setValue('activationMode', activationMode); if (isInjectionEnabled) { removeInjectedIframes(); injectHtmlCode(); } } function handleCustomStartFloorChange() { customStartFloor = parseInt(this.value); GM_setValue('customStartFloor', customStartFloor); if (isInjectionEnabled) { removeInjectedIframes(); injectHtmlCode(); } } function handleCustomEndFloorChange() { customEndFloor = parseInt(this.value); GM_setValue('customEndFloor', customEndFloor); if (isInjectionEnabled) { removeInjectedIframes(); injectHtmlCode(); } } function handleLastNFloorsChange() { customEndFloor = parseInt(this.value); GM_setValue('customEndFloor', customEndFloor); if (isInjectionEnabled) { removeInjectedIframes(); injectHtmlCode(); } } function handleDisplayModeChange(event) { displayMode = parseInt(event.target.value); GM_setValue('displayMode', displayMode); if (isInjectionEnabled) { removeInjectedIframes(); injectHtmlCode(); } } function handleToggleChange(e) { isInjectionEnabled = e.target.checked; document.getElementById('edge-injection-toggle').checked = isInjectionEnabled; if (isInjectionEnabled) { injectHtmlCode(); } else { removeInjectedIframes(); } } function handleMessage(event) { try { if (event.data === 'loaded') { const iframes = document.querySelectorAll('.mes_text iframe'); iframes.forEach(iframe => { if (iframe.contentWindow === event.source) { adjustIframeHeight(iframe); } }); } else if (event.data.type === 'buttonClick') { const buttonName = event.data.name; jQuery('.qr--button.menu_button').each(function () { if (jQuery(this).find('.qr--button-label').text().trim() === buttonName) { jQuery(this).click(); return false; } }); } else if (event.data.type === 'textInput') { const sendTextarea = document.getElementById('send_textarea'); if (sendTextarea) { sendTextarea.value = event.data.text; sendTextarea.dispatchEvent(new Event('input', { bubbles: true })); sendTextarea.dispatchEvent(new Event('change', { bubbles: true })); } } else if (event.data.type === 'sendClick') { const sendButton = document.getElementById('send_but'); if (sendButton) { sendButton.click(); } } } catch (error) { console.error('处理消息失败:', error); } } function handleDOMMutations(mutations) { for (const mutation of mutations) { if (mutation.type === 'childList') { for (const node of mutation.addedNodes) { if (node.nodeType === Node.ELEMENT_NODE && (node.classList.contains('mes_text') || node.querySelector('.mes_text'))) { if (isInjectionEnabled) { injectHtmlCode(); } break; } } } } } function checkLastMesTextChange() { const mesTextElements = document.getElementsByClassName('mes_text'); if (mesTextElements.length > 0) { const lastMesText = mesTextElements[mesTextElements.length - 1]; const codeElement = lastMesText.querySelector('code'); if (codeElement) { const currentContent = codeElement.innerText.trim(); const injectedIframe = lastMesText.querySelector('iframe'); if (currentContent !== lastMesTextContent || (isInjectionEnabled && !injectedIframe)) { lastMesTextContent = currentContent; if (isInjectionEnabled) { if (injectedIframe) { injectedIframe.remove(); } injectHtmlCode(lastMesText); const newIframe = lastMesText.querySelector('iframe'); if (newIframe) { newIframe.onload = function () { const currentTheme = getSystemTheme(); newIframe.contentWindow.postMessage({ type: 'themeChange', theme: currentTheme }, '*'); }; } } } } else { if (lastMesTextContent !== '') { lastMesTextContent = ''; const injectedIframe = lastMesText.querySelector('iframe'); if (injectedIframe) { injectedIframe.remove(); } } } } } // ---------------------------------------- 样式函数 ---------------------------------------- function addStyles() { const style = document.createElement('style'); style.textContent = ` /* 通用变量 */ :root { --smart-theme-blur-tint: var(--SmartThemeBlurTintColor, rgba(22, 11, 18, 0.73)); --smart-theme-body-color: var(--SmartThemeBodyColor, rgba(220, 220, 210, 1)); --smart-theme-border-color: var(--SmartThemeBorderColor, rgba(217, 90, 157, 0.5)); --smart-theme-button-bg: var(--SmartThemeButtonBG, rgba(74, 74, 74, 0.5)); --smart-theme-button-hover-bg: var(--SmartThemeButtonHoverBG, rgba(90, 90, 90, 0.7)); --smart-theme-blur-strength: var(--SmartThemeBlurStrength, 6px); } /* 边缘控制面板样式 */ #html-injector-edge-controls { position: fixed; right: -80px; top: 20vh; transition: right 0.3s ease-in-out; background-color: var(--smart-theme-blur-tint); border: 1px solid var(--smart-theme-border-color); border-radius: 10px 0 0 10px; padding: 10px; z-index: 9999; display: flex; flex-direction: column; align-items: center; width: 100px; color: var(--smart-theme-body-color); backdrop-filter: blur(var(--smart-theme-blur-strength)); } /* 开关样式 */ #html-injector-edge-controls .html-injector-switch { position: relative; display: inline-block; width: 50px; height: 24px; } #html-injector-edge-controls .html-injector-switch input { opacity: 0; width: 0; height: 0; } #html-injector-edge-controls .html-injector-slider { position: absolute; cursor: pointer; top: 0; left: 0; right: 0; bottom: 0; background-color: rgba(128, 128, 128, 0.3); transition: .2s; border-radius: 24px; } #html-injector-edge-controls .html-injector-slider:before { position: absolute; content: ""; height: 18px; width: 18px; left: 3px; bottom: 3px; background-color: var(--smart-theme-body-color); transition: .2s; border-radius: 50%; } #html-injector-edge-controls .html-injector-switch input:checked + .html-injector-slider { background-color: var(--smart-theme-border-color); } #html-injector-edge-controls .html-injector-switch input:checked + .html-injector-slider:before { transform: translateX(26px); } /* 按钮样式 */ #html-injector-edge-controls .html-injector-button { font-size: 14px; padding: 5px 10px; margin-top: 10px; width: 100%; text-align: center; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; background-color: var(--smart-theme-button-bg); color: var(--smart-theme-body-color); border: 1px solid var(--smart-theme-border-color); border-radius: 5px; transition: background-color 0.3s, color 0.3s; } #html-injector-edge-controls .html-injector-button:hover { background-color: var(--smart-theme-button-hover-bg); } /* 拖拽句柄样式 */ #html-injector-edge-controls #html-injector-drag-handle { width: 100%; height: 20px; background-color: var(--smart-theme-border-color); cursor: ns-resize; margin-bottom: 10px; border-radius: 5px 5px 0 0; display: flex; justify-content: center; align-items: center; } #html-injector-edge-controls #html-injector-drag-handle .drag-dots { display: flex; justify-content: space-between; width: 20px; height: 15px; } #html-injector-edge-controls #html-injector-drag-handle .drag-dots > div { display: flex; flex-direction: column; justify-content: space-between; } #html-injector-edge-controls #html-injector-drag-handle .drag-dots > div > div { width: 4px; height: 4px; border-radius: 50%; background-color: var(--smart-theme-body-color); } #html-injector-edge-controls #html-injector-drag-handle:hover .drag-dots > div > div { background-color: var(--smart-theme-button-hover-bg); } /* 设置面板样式 */ #html-injector-settings { position: fixed; top: 3vh; left: 50%; transform: translateX(-50%); width: 90%; max-width: 800px; height: auto; max-height: 90vh; background-color: var(--smart-theme-blur-tint); border: 1px solid var(--smart-theme-border-color); border-radius: 10px; padding: 20px; z-index: 10000; color: var(--smart-theme-body-color); backdrop-filter: blur(10px); display: flex; flex-direction: column; overflow-y: auto; } #html-injector-settings-header { display: flex; justify-content: space-between; align-items: center; padding-bottom: 15px; border-bottom: 1px solid var(--smart-theme-border-color); } #html-injector-close-settings { cursor: pointer; font-size: 24px; } #settings-content { flex-grow: 1; overflow-y: auto; padding-right: 10px; margin-top: 15px; max-height: calc(85vh - 150px); } .settings-footer { margin-top: 15px; padding-top: 15px; border-top: 1px solid var(--smart-theme-border-color); } /* 滚动条样式 */ #settings-content::-webkit-scrollbar { width: 8px; } #settings-content::-webkit-scrollbar-track { background: rgba(0, 0, 0, 0.1); border-radius: 4px; } #settings-content::-webkit-scrollbar-thumb { background: rgba(255, 255, 255, 0.3); border-radius: 4px; } #settings-content::-webkit-scrollbar-thumb:hover { background: rgba(255, 255, 255, 0.5); } /* 表单元素样式 */ #html-injector-settings #settings-content label { display: block; margin: 10px 0; color: var(--smart-theme-body-color); } #html-injector-settings #settings-content input[type="radio"] { margin-right: 5px; } #html-injector-settings #settings-content input[type="number"], #html-injector-settings #activation-mode, #html-injector-settings .theme-element { background-color: var(--smart-theme-blur-tint); color: var(--smart-theme-body-color); border: 1px solid var(--smart-theme-border-color); padding: 5px; border-radius: 3px; } #html-injector-settings #settings-content input[type="number"] { width: 50px; margin: 0 5px; } #html-injector-settings #settings-content input[type="number"]:focus, #html-injector-settings #activation-mode:focus, #html-injector-settings .theme-element:focus { outline: none; border-color: #0e639c; } #html-injector-settings .theme-element option { background-color: var(--smart-theme-blur-tint); } /* 其他样式 */ #html-injector-settings .settings-section { margin-bottom: 15px; } #html-injector-settings .settings-subtitle { font-size: 14px; margin: 0 0 5px 0; color: var(--smart-theme-body-color); } #html-injector-settings .settings-option { display: block; margin: 5px 0; font-size: 13px; } #html-injector-settings .settings-select { width: 100%; margin-bottom: 5px; } #html-injector-settings .settings-subsection { margin-top: 5px; padding-left: 10px; } #html-injector-settings .settings-note { font-size: 12px; color: #858585; margin: 2px 0; } #html-injector-settings .settings-footer { font-size: 12px; color: #858585; margin-top: 15px; } #html-injector-settings .code-example { background-color: var(--smart-theme-blur-tint); padding: 10px; border-radius: 3px; overflow-x: auto; font-size: 12px; color: var(--smart-theme-body-color); } /* 响应式设计 */ @media (max-width: 1000px) { #html-injector-settings { max-width: none; height: 50vh; max-height: none; } #settings-content { max-height: calc(80vh - 180px); } #html-injector-edge-controls { font-size: 10px; min-width: 100px; } #html-injector-edge-controls button { font-size: 12px; padding: 6px 10px; } #html-injector-edge-controls .html-injector-switch { width: 50px; height: 28px; } #html-injector-edge-controls .html-injector-slider:before { height: 20px; width: 20px; } #html-injector-edge-controls .html-injector-switch input:checked + .html-injector-slider:before { transform: translateX(22px); } } `; document.head.appendChild(style); } // ---------------------------------------- 初始化调用 ---------------------------------------- if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', initScript); } else { initScript(); } })();