【SillyTavern / ST酒馆】html代码注入器-改

可以让ST酒馆独立运行html代码 (Inject HTML code into SillyTavern pages.)

目前为 2024-11-01 提交的版本。查看 最新版本

  1. // ==UserScript==
  2. // @name 【SillyTavern / ST酒馆】html代码注入器-改
  3. // @name:zh 【ST酒馆】html代码注入器-改
  4. // @name:zh-CN 【ST酒馆】html代码注入器-改
  5. // @name:zh-TW 【ST酒館】html程式碼注入器-改
  6. // @name:ja 【SillyTavern】 HTMLコードインジェクター-改
  7. // @name:ko 【SillyTavern】 HTML코드 삽입기-수정
  8. // @name:en 【SillyTavern】 HTML Code Injector - Modified
  9. // @name:fr 【SillyTavern】 Injecteur de code HTML - Modifié
  10. // @name:de 【SillyTavern】 HTML-Code-Injektor - Modifiziert
  11. // @namespace https://gf.qytechs.cn/users/590339-miaotouy
  12. // @version 1.1.6
  13. // @description 可以让ST酒馆独立运行html代码 (Inject HTML code into SillyTavern pages.)
  14. // @description:zh 可以让ST酒馆独立运行html代码
  15. // @description:zh-CN 可以让ST酒馆独立运行html代码
  16. // @description:zh-TW 讓SillyTavern獨立運行html程式碼
  17. // @description:ja SillyTavernでhtmlコードを独立して実行できるようにします
  18. // @description:ko SillyTavern에서 HTML 코드를 독립적으로 실행할 수 있습니다.
  19. // @description:en Inject HTML code into SillyTavern pages.
  20. // @description:fr Permet d'exécuter du code HTML de manière indépendante dans SillyTavern.
  21. // @description:de Ermöglicht die unabhängige Ausführung von HTML-Code in SillyTavern.
  22. // @author Qianzhuo
  23. // @match *://localhost:8000/*
  24. // @match *://127.0.0.1:8000/*
  25. // @match *://*/*:8000/*
  26. // @match *://frp-kit.top:*/*
  27. // @include /^https?:\/\/.*:8000\//
  28. // @grant GM_setValue
  29. // @grant GM_getValue
  30. // @require https://code.jquery.com/jquery-3.6.0.min.js
  31. // @license CC BY-NC 4.0
  32. // ==/UserScript==
  33. /*
  34. 原作者:Qianzhuo
  35. 修改者:miaotouy
  36.  
  37. 【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/
  38. */
  39. (function () {
  40. 'use strict';
  41. // ---------------------------------------- 全局变量 ----------------------------------------
  42. let isInjectionEnabled, displayMode, lastMesTextContent, activationMode, customStartFloor, customEndFloor, savedPosition, isEdgeControlsCollapsed;
  43. let edgeControls, settingsPanel;
  44. // ---------------------------------------- 初始化函数 ----------------------------------------
  45. function initScript() {
  46. if (!document.title.includes('SillyTavern')) {
  47. console.log('页面标题不是 "SillyTavern",脚本未运行。');
  48. return;
  49. }
  50. initVariables();
  51. createUI();
  52. addEventListeners();
  53. addDragFunctionality();
  54. startObservers();
  55. console.log('HTML注入器脚本已初始化');
  56. }
  57. function initVariables() {
  58. isInjectionEnabled = false;
  59. displayMode = parseInt(GM_getValue('displayMode', 1));
  60. lastMesTextContent = '';
  61. activationMode = GM_getValue('activationMode', 'all');
  62. customStartFloor = GM_getValue('customStartFloor', 1);
  63. customEndFloor = GM_getValue('customEndFloor', -1);
  64. savedPosition = GM_getValue('edgeControlsPosition', 'top-right');
  65. isEdgeControlsCollapsed = GM_getValue('isEdgeControlsCollapsed', true);
  66. }
  67. // ---------------------------------------- UI 创建函数 ----------------------------------------
  68. function createUI() {
  69. createSettingsPanel();
  70. createEdgeControls();
  71. addStyles();
  72. }
  73. function createSettingsPanel() {
  74. settingsPanel = document.createElement('div');
  75. settingsPanel.id = 'html-injector-settings';
  76. settingsPanel.classList.add('drawer');
  77. settingsPanel.style.display = 'none';
  78. settingsPanel.innerHTML = `
  79. <div id="html-injector-settings-header" class="inline-drawer-header">
  80. <span class="inline-drawer-title">HTML注入器设置</span>
  81. <div id="html-injector-close-settings" class="inline-drawer-icon fa-solid fa-circle-xmark"></div>
  82. </div>
  83. <div id="settings-content">
  84. <div class="settings-section">
  85. <h3 class="settings-subtitle">边缘控制面板位置</h3>
  86. <select id="edge-controls-position" class="settings-select theme-element">
  87. <option value="top-right">界面右上角</option>
  88. <option value="right-three-quarters">界面右侧3/4位置</option>
  89. <option value="right-middle">界面右侧中间</option>
  90. </select>
  91. </div>
  92. <div class="settings-section">
  93. <h3 class="settings-subtitle">显示模式</h3>
  94. <label class="settings-option"><input type="radio" name="display-mode" value="1" ${displayMode === 1 ? 'checked' : ''}> 原代码和注入效果一起显示</label>
  95. <label class="settings-option"><input type="radio" name="display-mode" value="2" ${displayMode === 2 ? 'checked' : ''}> 原代码以摘要形式显示</label>
  96. <label class="settings-option"><input type="radio" name="display-mode" value="3" ${displayMode === 3 ? 'checked' : ''}> 隐藏原代码,只显示注入效果</label>
  97. </div>
  98. <div class="settings-section">
  99. <h3 class="settings-subtitle">激活楼层</h3>
  100. <select id="activation-mode" class="settings-select theme-element">
  101. <option value="all">全部楼层</option>
  102. <option value="first">第一层</option>
  103. <option value="last">最后一层</option>
  104. <option value="lastN">最后N层</option>
  105. <option value="custom">自定义楼层</option>
  106. </select>
  107. <div id="custom-floor-settings" class="settings-subsection" style="display: none;">
  108. <label class="settings-option">起始楼层: <input type="number" id="custom-start-floor" min="1" value="1"></label>
  109. <label class="settings-option">结束楼层: <input type="number" id="custom-end-floor" min="-1" value="-1"></label>
  110. <p class="settings-note">(-1 表示最后一层)</p>
  111. </div>
  112. <div id="last-n-settings" class="settings-subsection" style="display: none;">
  113. <label class="settings-option">最后 <input type="number" id="last-n-floors" min="1" value="1"> 层</label>
  114. </div>
  115. </div>
  116. </div>
  117. <div class="settings-footer">
  118. <p>安全提醒:请仅注入您信任的代码。不安全的代码可能会对您的系统造成潜在风险。</p>
  119. <p>注意:要注入的 HTML 代码应该用 \`\`\` 包裹,例如:</p>
  120. <pre class="code-example">
  121. \`\`\`
  122. &lt;h1&gt;Hello, World!&lt;/h1&gt;
  123. &lt;p&gt;This is an example.&lt;/p&gt;
  124. \`\`\`
  125. </pre>
  126. <p>以下是对应ST酒馆功能的特殊类名及简单的使用方法:</p>
  127. <pre class="code-example">
  128. \`\`\`
  129. &lt;button class="qr-button"&gt;(你的QR按钮名字)&lt;/button&gt;
  130. &lt;textarea class="st-text"&gt;(对应酒馆的输入文本框,输入内容会同步到酒馆的文本框里)&lt;/textarea&gt;
  131. &lt;button class="st-send-button"&gt;(对应酒馆的发送按钮)&lt;/button&gt;
  132. \`\`\`
  133. </pre>
  134. <p>【注意】通过JavaScript动态插入st-text框的内容同步到st酒馆的输入框需要处理时间,如果需要同步,请添加一个小延迟来确保文本有时间进行同步.</p>
  135. <a href="https://discord.com/channels/1134557553011998840/1271783456690409554" target="_blank"> Discord教程帖指路← 有详细说明与gal界面等模版 </a>
  136. </div>
  137. `;
  138. document.body.appendChild(settingsPanel);
  139. }
  140. function createEdgeControls() {
  141. edgeControls = document.createElement('div');
  142. edgeControls.id = 'html-injector-edge-controls';
  143. edgeControls.innerHTML = `
  144. <div id="html-injector-drag-handle">
  145. <div class="drag-dots"></div>
  146. </div>
  147. <label class="html-injector-switch">
  148. <input type="checkbox" id="edge-injection-toggle">
  149. <span class="html-injector-slider"></span>
  150. </label>
  151. <button id="html-injector-toggle-panel" class="html-injector-button menu_button">显示面板</button>
  152. `;
  153. document.body.appendChild(edgeControls);
  154. // 创建拖拽点
  155. const dragDots = edgeControls.querySelector('.drag-dots');
  156. for (let i = 0; i < 3; i++) {
  157. const column = document.createElement('div');
  158. column.style.display = 'flex';
  159. column.style.flexDirection = 'column';
  160. column.style.justifyContent = 'space-between';
  161. column.style.height = '15px'; // 调整高度以适应三个点
  162. for (let j = 0; j < 2; j++) {
  163. const dot = document.createElement('div');
  164. dot.style.width = '4px';
  165. dot.style.height = '4px';
  166. dot.style.borderRadius = '50%';
  167. dot.style.backgroundColor = 'var(--smart-theme-body-color)';
  168. column.appendChild(dot);
  169. }
  170. dragDots.appendChild(column);
  171. }
  172. const toggleEdgeControlsButton = document.createElement('button');
  173. toggleEdgeControlsButton.id = 'toggle-edge-controls';
  174. toggleEdgeControlsButton.textContent = '>>';
  175. toggleEdgeControlsButton.style.cssText = `
  176. position: absolute;
  177. left: -20px;
  178. top: 50%;
  179. transform: translateY(-50%);
  180. background-color: var(--SmartThemeBlurTintColor, rgba(22, 11, 18, 0.73));
  181. color: var(--SmartThemeBodyColor, rgba(220, 220, 210, 1));
  182. border: 1px solid var(--SmartThemeBorderColor, rgba(217, 90, 157, 0.5));
  183. border-radius: 5px 0 0 5px;
  184. cursor: pointer;
  185. padding: 5px;
  186. user-select: none;
  187. font-size: 12px;
  188. height: 60px;
  189. `;
  190. edgeControls.appendChild(toggleEdgeControlsButton);
  191. updateEdgeControlsPosition(savedPosition);
  192. updateEdgeControlsDisplay();
  193. }
  194. // ---------------------------------------- 事件监听器 ----------------------------------------
  195. function addEventListeners() {
  196. addSettingsPanelListeners();
  197. addEdgeControlsListeners();
  198. addGlobalListeners();
  199. document.getElementsByName('display-mode').forEach(radio => {
  200. radio.addEventListener('change', handleDisplayModeChange);
  201. });
  202. }
  203. function addSettingsPanelListeners() {
  204. document.getElementById('activation-mode').addEventListener('change', handleActivationModeChange);
  205. document.getElementById('custom-start-floor').addEventListener('change', handleCustomStartFloorChange);
  206. document.getElementById('custom-end-floor').addEventListener('change', handleCustomEndFloorChange);
  207. document.getElementById('last-n-floors').addEventListener('change', handleLastNFloorsChange);
  208. document.getElementsByName('display-mode').forEach(radio => {
  209. radio.addEventListener('change', handleDisplayModeChange);
  210. });
  211. document.getElementById('html-injector-close-settings').addEventListener('click', toggleSettingsPanel);
  212. }
  213. function addEdgeControlsListeners() {
  214. document.getElementById('edge-injection-toggle').addEventListener('change', handleToggleChange);
  215. document.getElementById('html-injector-toggle-panel').addEventListener('click', toggleSettingsPanel);
  216. document.getElementById('toggle-edge-controls').addEventListener('click', toggleEdgeControls);
  217. addDragFunctionality();
  218. }
  219. function addGlobalListeners() {
  220. window.addEventListener('message', handleMessage);
  221. window.addEventListener('resize', handleResize);
  222. window.matchMedia('(prefers-color-scheme: dark)').addListener(updateAllIframesTheme);
  223. }
  224. function addDragFunctionality() {
  225. const dragHandle = document.getElementById('html-injector-drag-handle');
  226. const edgeControls = document.getElementById('html-injector-edge-controls');
  227. let isDragging = false;
  228. let startY, startTop;
  229. function handleDragStart(e) {
  230. isDragging = true;
  231. startY = e.type.includes('mouse') ? e.clientY : e.touches[0].clientY;
  232. startTop = edgeControls.offsetTop;
  233. e.preventDefault();
  234. }
  235. function handleDragMove(e) {
  236. if (!isDragging) return;
  237. const clientY = e.type.includes('mouse') ? e.clientY : e.touches[0].clientY;
  238. let newTop = startTop + (clientY - startY);
  239. newTop = Math.max(0, Math.min(newTop, window.innerHeight - edgeControls.offsetHeight));
  240. edgeControls.style.top = newTop + 'px';
  241. }
  242. function handleDragEnd() {
  243. isDragging = false;
  244. }
  245. dragHandle.addEventListener('mousedown', handleDragStart);
  246. dragHandle.addEventListener('touchstart', handleDragStart);
  247. document.addEventListener('mousemove', handleDragMove);
  248. document.addEventListener('touchmove', handleDragMove);
  249. document.addEventListener('mouseup', handleDragEnd);
  250. document.addEventListener('touchend', handleDragEnd);
  251. }
  252.  
  253. // ---------------------------------------- 观察器和定时器 ----------------------------------------
  254. function startObservers() {
  255. const observer = new MutationObserver(handleDOMMutations);
  256. observer.observe(document.body, { childList: true, subtree: true });
  257. setInterval(checkLastMesTextChange, 2000);
  258. }
  259. // ---------------------------------------- 核心功能函数 ----------------------------------------
  260. function injectHtmlCode(specificMesText = null) {
  261. try {
  262. let mesTextElements = specificMesText ? [specificMesText] : Array.from(document.getElementsByClassName('mes_text'));
  263. let targetElements;
  264. switch (activationMode) {
  265. case 'first':
  266. targetElements = mesTextElements.slice(0, 1);
  267. break;
  268. case 'last':
  269. targetElements = mesTextElements.slice(-1);
  270. break;
  271. case 'lastN':
  272. targetElements = mesTextElements.slice(-customEndFloor);
  273. break;
  274. case 'custom': {
  275. const start = customStartFloor - 1;
  276. const end = customEndFloor === -1 ? undefined : customEndFloor;
  277. targetElements = mesTextElements.slice(start, end);
  278. break;
  279. }
  280. default: // 'all'
  281. targetElements = mesTextElements;
  282. }
  283. for (const mesText of targetElements) {
  284. const codeElements = mesText.getElementsByTagName('code');
  285. for (const codeElement of codeElements) {
  286. let htmlContent = codeElement.innerText.trim();
  287. if (htmlContent.startsWith('<') && htmlContent.endsWith('>')) {
  288. const iframe = document.createElement('iframe');
  289. iframe.style.width = '100%';
  290. iframe.style.border = 'none';
  291. iframe.style.marginTop = '10px';
  292. iframe.srcdoc = `
  293. <html>
  294. <head>
  295. <style>
  296. /* 自定义样式 */
  297. ::-webkit-scrollbar {
  298. width: 8px;
  299. height: 8px;
  300. }
  301. ::-webkit-scrollbar-track {
  302. background: rgba(0, 0, 0, 0.1);
  303. border-radius: 4px;
  304. }
  305. ::-webkit-scrollbar-thumb {
  306. background: rgba(0, 0, 0, 0.3);
  307. border-radius: 4px;
  308. }
  309. ::-webkit-scrollbar-thumb:hover {
  310. background: rgba(0, 0, 0, 0.5);
  311. }
  312. [data-theme="dark"] ::-webkit-scrollbar-track {
  313. background: rgba(255, 255, 255, 0.1);
  314. }
  315. [data-theme="dark"] ::-webkit-scrollbar-thumb {
  316. background: rgba(255, 255, 255, 0.3);
  317. }
  318. [data-theme="dark"] ::-webkit-scrollbar-thumb:hover {
  319. background: rgba(255, 255, 255, 0.5);
  320. }
  321. .container[data-theme="light"] {
  322. --bg-color: rgba(240, 240, 255, 0.1);
  323. --text-color: #1e1e1e;
  324. --border-color: rgba(139,226,115,0.3);
  325. --nav-bg-color: rgba(240,240,255,0.4);
  326. }
  327. .container[data-theme="dark"] {
  328. --bg-color: rgba(40, 40, 40, 0.2);
  329. --text-color: #e0e0e0;
  330. --border-color: rgba(74,74,74,0.3);
  331. --nav-bg-color: rgba(30,30,30,0.4);
  332. }
  333. .container {
  334. background-color: var(--bg-color);
  335. color: var(--text-color);
  336. }
  337. .container .left-nav {
  338. background-color: var(--nav-bg-color);
  339. }
  340. .container .button, .container .left-nav .section {
  341. border: 1px solid var(--border-color);
  342. }
  343. </style>
  344. </head>
  345. <body>
  346. <div class="theme-content">
  347. ${htmlContent}
  348. </div>
  349. <script>
  350. window.addEventListener('load', function() {
  351. window.parent.postMessage('loaded', '*');
  352. const darkModeMediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
  353. function handleThemeChange(e) {
  354. document.body.setAttribute('data-theme', e.matches ? 'dark' : 'light');
  355. window.parent.postMessage({type: 'themeChange', theme: e.matches ? 'dark' : 'light'}, '*');
  356. }
  357. darkModeMediaQuery.addListener(handleThemeChange);
  358. handleThemeChange(darkModeMediaQuery);
  359. document.querySelectorAll('.qr-button').forEach(button => {
  360. button.addEventListener('click', function() {
  361. const buttonName = this.textContent.trim();
  362. window.parent.postMessage({type: 'buttonClick', name: buttonName}, '*');
  363. });
  364. });
  365. document.querySelectorAll('.st-text').forEach(textarea => {
  366. textarea.addEventListener('input', function() {
  367. window.parent.postMessage({type: 'textInput', text: this.value}, '*');
  368. });
  369. textarea.addEventListener('change', function() {
  370. window.parent.postMessage({type: 'textInput', text: this.value}, '*');
  371. });
  372. const observer = new MutationObserver((mutations) => {
  373. mutations.forEach((mutation) => {
  374. if (mutation.type === 'attributes' && mutation.attributeName === 'value') {
  375. window.parent.postMessage({type: 'textInput', text: textarea.value}, '*');
  376. }
  377. });
  378. });
  379. observer.observe(textarea, { attributes: true });
  380. });
  381. document.querySelectorAll('.st-send-button').forEach(button => {
  382. button.addEventListener('click', function() {
  383. window.parent.postMessage({type: 'sendClick'}, '*');
  384. });
  385. });
  386. });
  387. window.addEventListener('message', function(event) {
  388. if (event.data.type === 'themeChange') {
  389. document.body.setAttribute('data-theme', event.data.theme);
  390. }
  391. });
  392. </script>
  393. </body>
  394. </html>
  395. `;
  396. if (displayMode === 2) {
  397. const details = document.createElement('details');
  398. const summary = document.createElement('summary');
  399. summary.textContent = '[原代码]';
  400. details.appendChild(summary);
  401. codeElement.parentNode.insertBefore(details, codeElement);
  402. details.appendChild(codeElement);
  403. } else if (displayMode === 3) {
  404. codeElement.style.display = 'none';
  405. }
  406. codeElement.parentNode.insertBefore(iframe, codeElement.nextSibling);
  407. iframe.onload = function () {
  408. adjustIframeHeight(iframe);
  409. setTimeout(() => adjustIframeHeight(iframe), 500);
  410. };
  411. if (iframe.contentWindow) {
  412. const resizeObserver = new ResizeObserver(() => adjustIframeHeight(iframe));
  413. resizeObserver.observe(iframe.contentWindow.document.body);
  414. }
  415. }
  416. }
  417. }
  418. } catch (error) {
  419. console.error('HTML注入失败:', error);
  420. }
  421. }
  422. function removeInjectedIframes() {
  423. const iframes = document.querySelectorAll('.mes_text iframe');
  424. iframes.forEach(iframe => iframe.remove());
  425. const codeElements = document.querySelectorAll('.mes_text code');
  426. codeElements.forEach(code => {
  427. code.style.display = '';
  428. const details = code.closest('details');
  429. if (details) {
  430. details.parentNode.insertBefore(code, details);
  431. details.remove();
  432. }
  433. });
  434. }
  435. // ---------------------------------------- 辅助函数 ----------------------------------------
  436. function adjustIframeHeight(iframe) {
  437. try {
  438. if (iframe.contentWindow.document.body) {
  439. const height = iframe.contentWindow.document.documentElement.scrollHeight;
  440. iframe.style.height = (height + 5) + 'px';
  441. }
  442. } catch (error) {
  443. console.error('调整iframe高度失败:', error);
  444. }
  445. }
  446. function getSystemTheme() {
  447. return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
  448. }
  449. function updateAllIframesTheme() {
  450. const iframes = document.querySelectorAll('.mes_text iframe');
  451. iframes.forEach(iframe => {
  452. try {
  453. if (iframe.contentWindow) {
  454. iframe.contentWindow.postMessage({ type: 'themeChange', theme: getSystemTheme() }, '*');
  455. }
  456. } catch (error) {
  457. console.error('更新iframe主题失败:', error);
  458. }
  459. });
  460. }
  461. function updateEdgeControlsPosition(position) {
  462. if (!edgeControls) return;
  463. switch (position) {
  464. case 'top-right':
  465. edgeControls.style.top = '20vh';
  466. edgeControls.style.transform = 'none';
  467. break;
  468. case 'right-three-quarters':
  469. edgeControls.style.top = '75vh';
  470. edgeControls.style.transform = 'none';
  471. break;
  472. case 'right-middle':
  473. edgeControls.style.top = '50%';
  474. edgeControls.style.transform = 'translateY(-50%)';
  475. break;
  476. }
  477. edgeControls.style.right = isEdgeControlsCollapsed ? '-100px' : '0';
  478. GM_setValue('edgeControlsPosition', position);
  479. }
  480. function updateEdgeControlsDisplay() {
  481. if (!edgeControls) return;
  482. edgeControls.style.right = isEdgeControlsCollapsed ? '-100px' : '0';
  483. const toggleButton = document.getElementById('toggle-edge-controls');
  484. if (toggleButton) {
  485. toggleButton.textContent = isEdgeControlsCollapsed ? '<<' : '>>';
  486. }
  487. }
  488. function toggleSettingsPanel() {
  489. const isVisible = settingsPanel.style.display === 'block';
  490. settingsPanel.style.display = isVisible ? 'none' : 'block';
  491. document.getElementById('html-injector-toggle-panel').textContent = isVisible ? '显示面板' : '隐藏面板';
  492. }
  493. function toggleEdgeControls() {
  494. isEdgeControlsCollapsed = !isEdgeControlsCollapsed;
  495. GM_setValue('isEdgeControlsCollapsed', isEdgeControlsCollapsed);
  496. updateEdgeControlsDisplay();
  497. }
  498. function handleResize() {
  499. updateEdgeControlsPosition(savedPosition);
  500. }
  501. function handleActivationModeChange() {
  502. const customSettings = document.getElementById('custom-floor-settings');
  503. const lastNSettings = document.getElementById('last-n-settings');
  504. customSettings.style.display = this.value === 'custom' ? 'block' : 'none';
  505. lastNSettings.style.display = this.value === 'lastN' ? 'block' : 'none';
  506. activationMode = this.value;
  507. GM_setValue('activationMode', activationMode);
  508. if (isInjectionEnabled) {
  509. removeInjectedIframes();
  510. injectHtmlCode();
  511. }
  512. }
  513. function handleCustomStartFloorChange() {
  514. customStartFloor = parseInt(this.value);
  515. GM_setValue('customStartFloor', customStartFloor);
  516. if (isInjectionEnabled) {
  517. removeInjectedIframes();
  518. injectHtmlCode();
  519. }
  520. }
  521. function handleCustomEndFloorChange() {
  522. customEndFloor = parseInt(this.value);
  523. GM_setValue('customEndFloor', customEndFloor);
  524. if (isInjectionEnabled) {
  525. removeInjectedIframes();
  526. injectHtmlCode();
  527. }
  528. }
  529. function handleLastNFloorsChange() {
  530. customEndFloor = parseInt(this.value);
  531. GM_setValue('customEndFloor', customEndFloor);
  532. if (isInjectionEnabled) {
  533. removeInjectedIframes();
  534. injectHtmlCode();
  535. }
  536. }
  537. function handleDisplayModeChange(event) {
  538. displayMode = parseInt(event.target.value);
  539. GM_setValue('displayMode', displayMode);
  540. if (isInjectionEnabled) {
  541. removeInjectedIframes();
  542. injectHtmlCode();
  543. }
  544. }
  545. function handleToggleChange(e) {
  546. isInjectionEnabled = e.target.checked;
  547. document.getElementById('edge-injection-toggle').checked = isInjectionEnabled;
  548. if (isInjectionEnabled) {
  549. injectHtmlCode();
  550. } else {
  551. removeInjectedIframes();
  552. }
  553. }
  554. function handleMessage(event) {
  555. try {
  556. if (event.data === 'loaded') {
  557. const iframes = document.querySelectorAll('.mes_text iframe');
  558. iframes.forEach(iframe => {
  559. if (iframe.contentWindow === event.source) {
  560. adjustIframeHeight(iframe);
  561. }
  562. });
  563. } else if (event.data.type === 'buttonClick') {
  564. const buttonName = event.data.name;
  565. jQuery('.qr--button.menu_button').each(function () {
  566. if (jQuery(this).find('.qr--button-label').text().trim() === buttonName) {
  567. jQuery(this).click();
  568. return false;
  569. }
  570. });
  571. } else if (event.data.type === 'textInput') {
  572. const sendTextarea = document.getElementById('send_textarea');
  573. if (sendTextarea) {
  574. sendTextarea.value = event.data.text;
  575. sendTextarea.dispatchEvent(new Event('input', { bubbles: true }));
  576. sendTextarea.dispatchEvent(new Event('change', { bubbles: true }));
  577. }
  578. } else if (event.data.type === 'sendClick') {
  579. const sendButton = document.getElementById('send_but');
  580. if (sendButton) {
  581. sendButton.click();
  582. }
  583. }
  584. } catch (error) {
  585. console.error('处理消息失败:', error);
  586. }
  587. }
  588. function handleDOMMutations(mutations) {
  589. for (const mutation of mutations) {
  590. if (mutation.type === 'childList') {
  591. for (const node of mutation.addedNodes) {
  592. if (node.nodeType === Node.ELEMENT_NODE &&
  593. (node.classList.contains('mes_text') || node.querySelector('.mes_text'))) {
  594. if (isInjectionEnabled) {
  595. injectHtmlCode();
  596. }
  597. break;
  598. }
  599. }
  600. }
  601. }
  602. }
  603. function checkLastMesTextChange() {
  604. const mesTextElements = document.getElementsByClassName('mes_text');
  605. if (mesTextElements.length > 0) {
  606. const lastMesText = mesTextElements[mesTextElements.length - 1];
  607. const codeElement = lastMesText.querySelector('code');
  608. if (codeElement) {
  609. const currentContent = codeElement.innerText.trim();
  610. const injectedIframe = lastMesText.querySelector('iframe');
  611. if (currentContent !== lastMesTextContent || (isInjectionEnabled && !injectedIframe)) {
  612. lastMesTextContent = currentContent;
  613. if (isInjectionEnabled) {
  614. if (injectedIframe) {
  615. injectedIframe.remove();
  616. }
  617. injectHtmlCode(lastMesText);
  618. const newIframe = lastMesText.querySelector('iframe');
  619. if (newIframe) {
  620. newIframe.onload = function () {
  621. const currentTheme = getSystemTheme();
  622. newIframe.contentWindow.postMessage({ type: 'themeChange', theme: currentTheme }, '*');
  623. };
  624. }
  625. }
  626. }
  627. } else {
  628. if (lastMesTextContent !== '') {
  629. lastMesTextContent = '';
  630. const injectedIframe = lastMesText.querySelector('iframe');
  631. if (injectedIframe) {
  632. injectedIframe.remove();
  633. }
  634. }
  635. }
  636. }
  637. }
  638. // ---------------------------------------- 样式函数 ----------------------------------------
  639. function addStyles() {
  640. const style = document.createElement('style');
  641. style.textContent = `
  642. /* 通用变量 */
  643. :root {
  644. --smart-theme-blur-tint: var(--SmartThemeBlurTintColor, rgba(22, 11, 18, 0.73));
  645. --smart-theme-body-color: var(--SmartThemeBodyColor, rgba(220, 220, 210, 1));
  646. --smart-theme-border-color: var(--SmartThemeBorderColor, rgba(217, 90, 157, 0.5));
  647. --smart-theme-button-bg: var(--SmartThemeButtonBG, rgba(74, 74, 74, 0.5));
  648. --smart-theme-button-hover-bg: var(--SmartThemeButtonHoverBG, rgba(90, 90, 90, 0.7));
  649. --smart-theme-blur-strength: var(--SmartThemeBlurStrength, 6px);
  650. }
  651. /* 边缘控制面板样式 */
  652. #html-injector-edge-controls {
  653. position: fixed;
  654. right: -80px;
  655. top: 20vh;
  656. transition: right 0.3s ease-in-out;
  657. background-color: var(--smart-theme-blur-tint);
  658. border: 1px solid var(--smart-theme-border-color);
  659. border-radius: 10px 0 0 10px;
  660. padding: 10px;
  661. z-index: 9999;
  662. display: flex;
  663. flex-direction: column;
  664. align-items: center;
  665. width: 100px;
  666. color: var(--smart-theme-body-color);
  667. backdrop-filter: blur(var(--smart-theme-blur-strength));
  668. }
  669. /* 开关样式 */
  670. #html-injector-edge-controls .html-injector-switch {
  671. position: relative;
  672. display: inline-block;
  673. width: 50px;
  674. height: 24px;
  675. }
  676. #html-injector-edge-controls .html-injector-switch input {
  677. opacity: 0;
  678. width: 0;
  679. height: 0;
  680. }
  681. #html-injector-edge-controls .html-injector-slider {
  682. position: absolute;
  683. cursor: pointer;
  684. top: 0;
  685. left: 0;
  686. right: 0;
  687. bottom: 0;
  688. background-color: rgba(128, 128, 128, 0.3);
  689. transition: .2s;
  690. border-radius: 24px;
  691. }
  692. #html-injector-edge-controls .html-injector-slider:before {
  693. position: absolute;
  694. content: "";
  695. height: 18px;
  696. width: 18px;
  697. left: 3px;
  698. bottom: 3px;
  699. background-color: var(--smart-theme-body-color);
  700. transition: .2s;
  701. border-radius: 50%;
  702. }
  703. #html-injector-edge-controls .html-injector-switch input:checked + .html-injector-slider {
  704. background-color: var(--smart-theme-border-color);
  705. }
  706. #html-injector-edge-controls .html-injector-switch input:checked + .html-injector-slider:before {
  707. transform: translateX(26px);
  708. }
  709. /* 按钮样式 */
  710. #html-injector-edge-controls .html-injector-button {
  711. font-size: 14px;
  712. padding: 5px 10px;
  713. margin-top: 10px;
  714. width: 100%;
  715. text-align: center;
  716. white-space: nowrap;
  717. overflow: hidden;
  718. text-overflow: ellipsis;
  719. background-color: var(--smart-theme-button-bg);
  720. color: var(--smart-theme-body-color);
  721. border: 1px solid var(--smart-theme-border-color);
  722. border-radius: 5px;
  723. transition: background-color 0.3s, color 0.3s;
  724. }
  725. #html-injector-edge-controls .html-injector-button:hover {
  726. background-color: var(--smart-theme-button-hover-bg);
  727. }
  728. /* 拖拽句柄样式 */
  729. #html-injector-edge-controls #html-injector-drag-handle {
  730. width: 100%;
  731. height: 20px;
  732. background-color: var(--smart-theme-border-color);
  733. cursor: ns-resize;
  734. margin-bottom: 10px;
  735. border-radius: 5px 5px 0 0;
  736. display: flex;
  737. justify-content: center;
  738. align-items: center;
  739. }
  740. #html-injector-edge-controls #html-injector-drag-handle .drag-dots {
  741. display: flex;
  742. justify-content: space-between;
  743. width: 20px;
  744. height: 15px;
  745. }
  746. #html-injector-edge-controls #html-injector-drag-handle .drag-dots > div {
  747. display: flex;
  748. flex-direction: column;
  749. justify-content: space-between;
  750. }
  751. #html-injector-edge-controls #html-injector-drag-handle .drag-dots > div > div {
  752. width: 4px;
  753. height: 4px;
  754. border-radius: 50%;
  755. background-color: var(--smart-theme-body-color);
  756. }
  757. #html-injector-edge-controls #html-injector-drag-handle:hover .drag-dots > div > div {
  758. background-color: var(--smart-theme-button-hover-bg);
  759. }
  760. /* 设置面板样式 */
  761. #html-injector-settings {
  762. position: fixed;
  763. top: 3vh;
  764. left: 50%;
  765. transform: translateX(-50%);
  766. width: 90%;
  767. max-width: 800px;
  768. height: auto;
  769. max-height: 90vh;
  770. background-color: var(--smart-theme-blur-tint);
  771. border: 1px solid var(--smart-theme-border-color);
  772. border-radius: 10px;
  773. padding: 20px;
  774. z-index: 10000;
  775. color: var(--smart-theme-body-color);
  776. backdrop-filter: blur(10px);
  777. display: flex;
  778. flex-direction: column;
  779. overflow-y: auto;
  780. }
  781. #html-injector-settings-header {
  782. display: flex;
  783. justify-content: space-between;
  784. align-items: center;
  785. padding-bottom: 15px;
  786. border-bottom: 1px solid var(--smart-theme-border-color);
  787. }
  788. #html-injector-close-settings {
  789. cursor: pointer;
  790. font-size: 24px;
  791. }
  792. #settings-content {
  793. flex-grow: 1;
  794. overflow-y: auto;
  795. padding-right: 10px;
  796. margin-top: 15px;
  797. max-height: calc(85vh - 150px);
  798. }
  799. .settings-footer {
  800. margin-top: 15px;
  801. padding-top: 15px;
  802. border-top: 1px solid var(--smart-theme-border-color);
  803. }
  804. /* 滚动条样式 */
  805. #settings-content::-webkit-scrollbar {
  806. width: 8px;
  807. }
  808. #settings-content::-webkit-scrollbar-track {
  809. background: rgba(0, 0, 0, 0.1);
  810. border-radius: 4px;
  811. }
  812. #settings-content::-webkit-scrollbar-thumb {
  813. background: rgba(255, 255, 255, 0.3);
  814. border-radius: 4px;
  815. }
  816. #settings-content::-webkit-scrollbar-thumb:hover {
  817. background: rgba(255, 255, 255, 0.5);
  818. }
  819. /* 表单元素样式 */
  820. #html-injector-settings #settings-content label {
  821. display: block;
  822. margin: 10px 0;
  823. color: var(--smart-theme-body-color);
  824. }
  825. #html-injector-settings #settings-content input[type="radio"] {
  826. margin-right: 5px;
  827. }
  828. #html-injector-settings #settings-content input[type="number"],
  829. #html-injector-settings #activation-mode,
  830. #html-injector-settings .theme-element {
  831. background-color: var(--smart-theme-blur-tint);
  832. color: var(--smart-theme-body-color);
  833. border: 1px solid var(--smart-theme-border-color);
  834. padding: 5px;
  835. border-radius: 3px;
  836. }
  837. #html-injector-settings #settings-content input[type="number"] {
  838. width: 50px;
  839. margin: 0 5px;
  840. }
  841. #html-injector-settings #settings-content input[type="number"]:focus,
  842. #html-injector-settings #activation-mode:focus,
  843. #html-injector-settings .theme-element:focus {
  844. outline: none;
  845. border-color: #0e639c;
  846. }
  847. #html-injector-settings .theme-element option {
  848. background-color: var(--smart-theme-blur-tint);
  849. }
  850. /* 其他样式 */
  851. #html-injector-settings .settings-section {
  852. margin-bottom: 15px;
  853. }
  854. #html-injector-settings .settings-subtitle {
  855. font-size: 14px;
  856. margin: 0 0 5px 0;
  857. color: var(--smart-theme-body-color);
  858. }
  859. #html-injector-settings .settings-option {
  860. display: block;
  861. margin: 5px 0;
  862. font-size: 13px;
  863. }
  864. #html-injector-settings .settings-select {
  865. width: 100%;
  866. margin-bottom: 5px;
  867. }
  868. #html-injector-settings .settings-subsection {
  869. margin-top: 5px;
  870. padding-left: 10px;
  871. }
  872. #html-injector-settings .settings-note {
  873. font-size: 12px;
  874. color: #858585;
  875. margin: 2px 0;
  876. }
  877. #html-injector-settings .settings-footer {
  878. font-size: 12px;
  879. color: #858585;
  880. margin-top: 15px;
  881. }
  882. #html-injector-settings .code-example {
  883. background-color: var(--smart-theme-blur-tint);
  884. padding: 10px;
  885. border-radius: 3px;
  886. overflow-x: auto;
  887. font-size: 12px;
  888. color: var(--smart-theme-body-color);
  889. }
  890. /* 响应式设计 */
  891. @media (max-width: 1000px) {
  892. #html-injector-settings {
  893. max-width: none;
  894. height: 50vh;
  895. max-height: none;
  896. }
  897. #settings-content {
  898. max-height: calc(80vh - 180px);
  899. }
  900. #html-injector-edge-controls {
  901. font-size: 10px;
  902. min-width: 100px;
  903. }
  904. #html-injector-edge-controls button {
  905. font-size: 12px;
  906. padding: 6px 10px;
  907. }
  908. #html-injector-edge-controls .html-injector-switch {
  909. width: 50px;
  910. height: 28px;
  911. }
  912. #html-injector-edge-controls .html-injector-slider:before {
  913. height: 20px;
  914. width: 20px;
  915. }
  916. #html-injector-edge-controls .html-injector-switch input:checked + .html-injector-slider:before {
  917. transform: translateX(22px);
  918. }
  919. }
  920. `;
  921. document.head.appendChild(style);
  922. }
  923. // ---------------------------------------- 初始化调用 ----------------------------------------
  924. if (document.readyState === 'loading') {
  925. document.addEventListener('DOMContentLoaded', initScript);
  926. } else {
  927. initScript();
  928. }
  929. })();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址