Greasy Fork

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

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

// ==UserScript==
// @name         【SillyTavern / ST酒馆】html代码注入器
// @name:zh      【ST酒馆】html代码注入器
// @name:zh-CN   【ST酒馆】html代码注入器
// @name:en      【SillyTavern】 HTML Code Injector
// @namespace    https://greasyfork.org/users/Qianzhuo
// @version      1.1.2
// @description  可以让ST酒馆独立运行html代码 (Inject HTML code into SillyTavern pages.)
// @description:zh  可以让ST酒馆独立运行html代码
// @description:zh-CN  可以让ST酒馆独立运行html代码
// @description:en  Inject HTML code into SillyTavern pages.
// @author       Qianzhuo
// @match        *://localhost:8000/*
// @match        *://127.0.0.1:8000/*
// @match        *://*/*:8000/*
// @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
// ==/UserScript==

/*
【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 = false;
  let displayMode = GM_getValue('displayMode', 1); // 从存储中获取,默认为1
  let lastMesTextContent = '';

  // 存储激活楼层的设置
  let activationMode = GM_getValue('activationMode', 'all'); // 默认激活所有楼层
  let customStartFloor = GM_getValue('customStartFloor', 1);
  let customEndFloor = GM_getValue('customEndFloor', -1); // -1 表示最后一层


  // 创建设置面板
  const settingsPanel = document.createElement('div');
  settingsPanel.innerHTML = `
    <div id="settings-header" style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px;">
        <span style="font-size: 16px; font-weight: bold;">HTML注入器设置</span>
        <button id="close-settings" class="close-button">×</button>
    </div>
    <div id="settings-content">
        <div class="settings-section">
            <h3 class="settings-subtitle">边缘控制面板位置</h3>
            <select id="edge-controls-position" class="settings-select">
                <option value="top-right">界面右上角</option>
                <option value="right-three-quarters">界面右侧3/4位置</option>
                <option value="right-middle">界面右侧中间</option>
            </select>
        </div>

        <div class="settings-section">
            <h3 class="settings-subtitle">显示模式</h3>
            <label class="settings-option"><input type="radio" name="display-mode" value="1"> 原代码和注入效果一起显示</label>
            <label class="settings-option"><input type="radio" name="display-mode" value="2"> 原代码以摘要形式显示</label>
            <label class="settings-option"><input type="radio" name="display-mode" value="3"> 隐藏原代码,只显示注入效果</label>
        </div>

        <div class="settings-section">
            <h3 class="settings-subtitle">激活楼层</h3>
            <select id="activation-mode" class="settings-select">
                <option value="all">全部楼层</option>
                <option value="first">第一层</option>
                <option value="last">最后一层</option>
                <option value="lastN">最后N层</option>
                <option value="custom">自定义楼层</option>
            </select>
            <div id="custom-floor-settings" class="settings-subsection" style="display: none;">
                <label class="settings-option">起始楼层: <input type="number" id="custom-start-floor" min="1" value="1"></label>
                <label class="settings-option">结束楼层: <input type="number" id="custom-end-floor" min="-1" value="-1"></label>
                <p class="settings-note">(-1 表示最后一层)</p>
            </div>
            <div id="last-n-settings" class="settings-subsection" style="display: none;">
                <label class="settings-option">最后 <input type="number" id="last-n-floors" min="1" value="1"> 层</label>
            </div>
        </div>
    </div>
    <div class="settings-footer">
        <p>安全提醒:请仅注入您信任的代码。不安全的代码可能会对您的系统造成潜在风险。</p>
        <p>注意:要注入的 HTML 代码应该用 \`\`\` 包裹,例如:</p>
        <pre class="code-example">
\`\`\`
&lt;h1&gt;Hello, World!&lt;/h1&gt;
&lt;p&gt;This is an example.&lt;/p&gt;
\`\`\`
        </pre>

        <p>以下是对应ST酒馆功能的特殊类名及简单的使用方法:</p>
        <pre class="code-example">
\`\`\`
&lt;button class="qr-button"&gt;(你的QR按钮名字)&lt;/button&gt;
&lt;textarea class="st-text"&gt;(对应酒馆的输入文本框,输入内容会同步到酒馆的文本框里)&lt;/textarea&gt;
&lt;button class="st-send-button"&gt;(对应酒馆的发送按钮)&lt;/button&gt;
&lt;audio class="st-audio" controls&gt;
    &lt;source src="你的音频文件地址" type="audio/类型"&gt;
&lt;/audio&gt;
(st-audio可以添加controls显示控制栏,loop循环播放,autoplay自动播放。同一时间只会播放一个音频)
<details>
  <summary>点击查看 st-audio 的详细用法讲解</summary>
【属性说明】
- class="st-audio" - 用于标识这个音频元素,使其受到我们刚才编写的音频管理系统控制
- loop - 使音频循环播放
- controls - 显示音频控制面板(播放/暂停/进度条等)
- autoplay - 尝试自动播放(注意:现代浏览器可能会阻止自动播放)
【type属性的作用】
- 告诉浏览器音频文件的格式,帮助浏览器更快地确定是否支持该格式
- 不同格式对应不同的type值:
  - .mp3 → type = "audio/mpeg"
  - .wav → type = "audio/wav"
  - .ogg → type = "audio/ogg"
  - .m4a → type = "audio/mp4"
【示例代码】
\`\`\`
&lt;audio class="st-audio" loop controls autoplay&gt;
    &lt;source src="https://tuchuang-93f.pages.dev/img/zeus_bgm3.wav" type="audio/wav"&gt;
&lt;/audio&gt;
\`\`\`
</details>
\`\`\`
        </pre>
        <p>【注意】通过JavaScript动态插入st-text框的内容同步到st酒馆的输入框需要处理时间,如果需要同步,请添加一个小延迟来确保文本有时间进行同步.</p>
        <a href="https://discord.com/channels/1134557553011998840/1271783456690409554" target="_blank"> →Discord教程帖指路← 有详细说明与gal界面等模版 </a>
    </div>
`;

  settingsPanel.id = 'html-injector-settings';
  settingsPanel.style.cssText = `
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    background-color: #1e1e1e;
    border-bottom: 1px solid #454545;
    padding: 20px;
    z-index: 9999;
    font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
    box-shadow: 0 2px 10px rgba(0,0,0,0.3);
    display: none;
    color: #d4d4d4;
    overflow-y: auto;
    max-height: 50vh;
`;
  document.body.appendChild(settingsPanel);

  // 处理激活楼层的设置
  document.getElementById('activation-mode').addEventListener('change', function () {
    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();
    }
  });

  document.getElementById('custom-start-floor').addEventListener('change', function () {
    customStartFloor = parseInt(this.value);
    GM_setValue('customStartFloor', customStartFloor);
    if (isInjectionEnabled) {
      removeInjectedIframes();
      injectHtmlCode();
    }
  });

  document.getElementById('custom-end-floor').addEventListener('change', function () {
    customEndFloor = parseInt(this.value);
    GM_setValue('customEndFloor', customEndFloor);
    if (isInjectionEnabled) {
      removeInjectedIframes();
      injectHtmlCode();
    }
  });

  document.getElementById('last-n-floors').addEventListener('change', function () {
    customEndFloor = parseInt(this.value);
    GM_setValue('customEndFloor', customEndFloor);
    if (isInjectionEnabled) {
      removeInjectedIframes();
      injectHtmlCode();
    }
  });



  // 创建开关
  function createToggleSwitch(id) {
    const toggleSwitch = document.createElement('label');
    toggleSwitch.className = 'switch';
    toggleSwitch.innerHTML = `
        <input type="checkbox" id="${id}">
        <span class="slider round"></span>
    `;
    toggleSwitch.style.cssText = `
        position: relative;
        display: inline-block;
        width: 60px;
        height: 34px;
    `;
    return toggleSwitch;
  }

  // 创建边缘控制面板
  const edgeControls = document.createElement('div');
  edgeControls.id = 'edge-controls';
  edgeControls.style.cssText = `
    position: fixed;
    right: 0;
    background-color: #2d2d2d;
    border: 1px solid #454545;
    border-right: none;
    border-radius: 5px 0 0 5px;
    padding: 10px;
    z-index: 9998;
    display: flex;
    flex-direction: column;
    align-items: center;
    min-width: 80px;  // 增加最小宽度
`;

  // 在边缘控制面板中添加开关
  const edgeSwitch = createToggleSwitch('edge-injection-toggle');
  edgeControls.appendChild(edgeSwitch);

  // 处理边缘控制面板的位置调整
  function updateEdgeControlsPosition(position) {
    const vh = window.innerHeight / 100;
    edgeControls.style.transform = 'none'; // 重置transform
    switch (position) {
      case 'top-right':
        edgeControls.style.top = '1vh';
        edgeControls.style.bottom = 'auto';
        break;
      case 'right-three-quarters':
        edgeControls.style.top = '25vh';
        edgeControls.style.bottom = 'auto';
        break;
      case 'right-middle':
        edgeControls.style.top = '50vh';
        edgeControls.style.transform = 'translateY(-50%)';
        edgeControls.style.bottom = 'auto';
        break;
    }
    GM_setValue('edgeControlsPosition', position);
    // 恢复收起/展开状态
    updateEdgeControlsDisplay();
  }

  // 位置调整事件监听器
  document.getElementById('edge-controls-position').addEventListener('change', function () {
    updateEdgeControlsPosition(this.value);
  });


  // 添加显示/隐藏面板的按钮
  const togglePanelButton = document.createElement('button');
  togglePanelButton.textContent = '显示面板';
  togglePanelButton.style.cssText = `
    margin-top: 10px;
    padding: 8px 12px;
    background-color: #0e639c;
    color: #ffffff;
    border: none;
    border-radius: 5px;
    cursor: pointer;
    font-size: 14px;
    font-weight: bold;
    width: 100%;
    text-align: center;
    transition: background-color 0.3s;
`;
  togglePanelButton.addEventListener('mouseover', function () {
    this.style.backgroundColor = '#1177bb';
  });
  togglePanelButton.addEventListener('mouseout', function () {
    this.style.backgroundColor = '#0e639c';
  });
  edgeControls.appendChild(togglePanelButton);

  // 添加收起/展开按钮
  const toggleEdgeControlsButton = document.createElement('button');
  toggleEdgeControlsButton.textContent = '<<';
  toggleEdgeControlsButton.style.cssText = `
    position: absolute;
    left: -15px;
    top: 50%;
    transform: translateY(-50%);
    background-color: #2d2d2d;
    color: #ffffff;
    border: none;
    border-radius: 3px 0 0 3px;
    cursor: pointer;
    padding: 3px;
    user-select: none;
    font-size: 10px;
`;
  edgeControls.appendChild(toggleEdgeControlsButton);

  document.body.appendChild(edgeControls);



  // 添加收起/展开功能
  // let isEdgeControlsCollapsed = false;
  let isEdgeControlsCollapsed = GM_getValue('isEdgeControlsCollapsed', false);
  toggleEdgeControlsButton.addEventListener('click', toggleEdgeControls);

  function toggleEdgeControls() {
    isEdgeControlsCollapsed = !isEdgeControlsCollapsed;
    GM_setValue('isEdgeControlsCollapsed', isEdgeControlsCollapsed);
    updateEdgeControlsDisplay();
  }

  function updateEdgeControlsDisplay() {
    edgeControls.style.transform = isEdgeControlsCollapsed ? 'translateX(calc(100% - 20px))' : 'translateX(0)';
    toggleEdgeControlsButton.textContent = isEdgeControlsCollapsed ? '>>' : '<<';
  }


  // 添加窗口大小变化的监听,确保面板始终在视图内
  window.addEventListener('resize', () => {
    const savedPosition = GM_getValue('edgeControlsPosition', 'top-right');
    updateEdgeControlsPosition(savedPosition);
  });

  // 添加样式
  const style = document.createElement('style');
  style.textContent = `
    .switch input {
        opacity: 0;
        width: 0;
        height: 0;
    }
    .slider {
        position: absolute;
        cursor: pointer;
        top: 0;
        left: 0;
        right: 0;
        bottom: 0;
        background-color: #3a3a3a;
        transition: .4s;
        border-radius: 34px;
    }
    .slider:before {
        position: absolute;
        content: "";
        height: 26px;
        width: 26px;
        left: 4px;
        bottom: 4px;
        background-color: #d4d4d4;
        transition: .4s;
        border-radius: 50%;
    }
    input:checked + .slider {
        background-color: #0e639c;
    }
    input:checked + .slider:before {
        transform: translateX(26px);
    }
    #settings-content label {
        display: block;
        margin: 10px 0;
        color: #d4d4d4;
    }

    .close-button {
        width: 30px;
        height: 30px;
        background-color: #e81123;
        border: none;
        color: white;
        font-size: 20px;
        font-weight: bold;
        cursor: pointer;
        display: flex;
        align-items: center;
        justify-content: center;
        border-radius: 50%;
        transition: background-color 0.3s;
    }

    .close-button:hover {
        background-color: #f1707a;
    }

    #settings-header {
        padding-bottom: 10px;
        border-bottom: 1px solid #454545;
        margin-bottom: 15px;
    }

    #settings-content input[type="radio"] {
        margin-right: 5px;
    }

    #settings-content input[type="number"] {
        background-color: #2d2d2d;
        color: #d4d4d4;
        border: 1px solid #454545;
        padding: 5px;
        border-radius: 3px;
        width: 50px;
        margin: 0 5px;
    }

    #settings-content input[type="number"]:focus {
        outline: none;
        border-color: #0e639c;
    }

    #activation-mode {
        background-color: #2d2d2d;
        color: #d4d4d4;
        border: 1px solid #454545;
        padding: 5px;
        border-radius: 3px;
    }

    #activation-mode:focus {
        outline: none;
        border-color: #0e639c;
    }

    .settings-section {
        margin-bottom: 15px;
    }

    .settings-subtitle {
        font-size: 14px;
        margin: 0 0 5px 0;
        color: #d4d4d4;
    }

    .settings-option {
        display: block;
        margin: 5px 0;
        font-size: 13px;
    }

    .settings-select {
        width: 100%;
        margin-bottom: 5px;
    }

    .settings-subsection {
        margin-top: 5px;
        padding-left: 10px;
    }

    .settings-note {
        font-size: 12px;
        color: #858585;
        margin: 2px 0;
    }

    .settings-footer {
        font-size: 12px;
        color: #858585;
        margin-top: 15px;
    }

    .code-example {
        background-color: #2d2d2d;
        padding: 10px;
        border-radius: 3px;
        overflow-x: auto;
        font-size: 12px;
    }

    // 响应式样式
    @media (max-width: 768px) {
    #edge-controls {
        font-size: 10px;
        min-width: 100px;
    }
    #edge-controls button {
        font-size: 12px;
        padding: 6px 10px;
    }
    .switch {
        width: 50px;
        height: 28px;
    }
    .slider:before {
        height: 20px;
        width: 20px;
    }
    input:checked + .slider:before {
        transform: translateX(22px);
    }
}

`;
  document.head.appendChild(style);

  // 监听开关变化
  function handleToggleChange(e) {
    isInjectionEnabled = e.target.checked;
    document.getElementById('edge-injection-toggle').checked = isInjectionEnabled;
    if (isInjectionEnabled) {
      injectHtmlCode();
    } else {
      removeInjectedIframes();
    }
  }

  document.getElementById('edge-injection-toggle').addEventListener('change', handleToggleChange);

  // 监听显示模式变化
  document.getElementsByName('display-mode').forEach(radio => {
    radio.addEventListener('change', function () {
      displayMode = parseInt(this.value);
      GM_setValue('displayMode', displayMode); // 保存设置
      if (isInjectionEnabled) {
        removeInjectedIframes();
        injectHtmlCode();
      }
    });
  });

  // 显示/隐藏面板按钮
  togglePanelButton.addEventListener('click', function () {
    if (settingsPanel.style.display === 'none') {
      settingsPanel.style.display = 'block';
      this.textContent = '隐藏面板';
    } else {
      settingsPanel.style.display = 'none';
      this.textContent = '显示面板';
    }
  });

  // 关闭设置面板
  document.getElementById('close-settings').addEventListener('click', function () {
    settingsPanel.style.display = 'none';
  });

  // 全局消息监听器
  window.addEventListener('message', function (event) {
    if (event.data === 'loaded') {
      // 处理 iframe 加载完成的消息
      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; // 退出 each 循环
        }
      });
    } else if (event.data.type === 'textInput') {
      // 处理文本输入
      const sendTextarea = document.getElementById('send_textarea');
      if (sendTextarea) {
        sendTextarea.value = event.data.text;
        // 触发 input 事件以确保任何监听器都能捕捉到变化
        sendTextarea.dispatchEvent(new Event('input', { bubbles: true }));
        // 如果需要,也可以触发 change 事件
        sendTextarea.dispatchEvent(new Event('change', { bubbles: true }));
      }
    } else if (event.data.type === 'sendClick') {
      // 处理发送按钮点击
      const sendButton = document.getElementById('send_but');
      if (sendButton) {
        sendButton.click();
      }
    }
  });

  // 添加一个自定义的 :contains 选择器
  jQuery.expr[':'].contains = function (a, i, m) {
    return jQuery(a).text().toUpperCase().indexOf(m[3].toUpperCase()) >= 0;
  };

  // 调整 iframe 高度的函数
  function adjustIframeHeight(iframe) {
    if (iframe.contentWindow.document.body) {
      const height = iframe.contentWindow.document.documentElement.scrollHeight;
      iframe.style.height = (height + 5) + 'px'; // 添加一些额外的高度
    }
  }

  // 主要的注入函数
  function injectHtmlCode(specificMesText = null) {
    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('>')) {
          // 创建一个iframe来运行HTML代码
          const iframe = document.createElement('iframe');

          // 确保每个iframe都有唯一的ID
          iframe.id = 'audio-iframe-' + Math.random().toString(36).substr(2, 9);

          iframe.style.width = '100%';
          iframe.style.height = '100%';
          iframe.style.border = 'none';
          iframe.style.marginTop = '10px';

          // 设置 iframe 的内容
          iframe.srcdoc = `
        <html>
            <head>
                <style>
                    body { margin: 0; padding: 0; }
                    /* 您可以在这里添加默认样式 */
                </style>
            </head>
            <body>
                ${htmlContent}
                <script>

            // 音频管理系统
            class AudioManager {
                constructor() {
                    this.currentlyPlaying = null;
                }

                handlePlay(audio) {
                    // 如果有其他音频在播放,先通知父窗口停止它
                    if (this.currentlyPlaying && this.currentlyPlaying !== audio) {
                        this.currentlyPlaying.pause();
                    }

                    // 通知父窗口有新的音频开始播放
                    window.parent.postMessage({
                        type: 'audioPlay',
                        iframeId: window.frameElement.id
                    }, '*');

                    this.currentlyPlaying = audio;
                }

                stopAll() {
                    if (this.currentlyPlaying) {
                        this.currentlyPlaying.pause();
                        this.currentlyPlaying = null;
                    }
                }
            }

            const audioManager = new AudioManager();

                    window.addEventListener('load', function() {
                        window.parent.postMessage('loaded', '*');

                        document.querySelectorAll('.qr-button').forEach(button => {
                            button.addEventListener('click', function() {
                                const buttonName = this.textContent.trim();
                                window.parent.postMessage({type: 'buttonClick', name: buttonName}, '*');
                            });
                        });

                        document.querySelectorAll('.st-text').forEach(textarea => {
                            textarea.addEventListener('input', function() {
                                window.parent.postMessage({type: 'textInput', text: this.value}, '*');
                            });

                            // 添加 'change' 事件监听
                            textarea.addEventListener('change', function() {
                                window.parent.postMessage({type: 'textInput', text: this.value}, '*');
                            });

                            // 添加一个 MutationObserver 来监听值的变化
                            const observer = new MutationObserver((mutations) => {
                                mutations.forEach((mutation) => {
                                    if (mutation.type === 'attributes' && mutation.attributeName === 'value') {
                                        window.parent.postMessage({type: 'textInput', text: textarea.value}, '*');
                                    }
                                });
                            });

                            observer.observe(textarea, { attributes: true });
                        });

                        document.querySelectorAll('.st-send-button').forEach(button => {
                            button.addEventListener('click', function() {
                                window.parent.postMessage({type: 'sendClick'}, '*');
                            });
                        });

                        // 为所有st-audio类的音频元素添加事件监听
                        document.querySelectorAll('.st-audio').forEach(audio => {
                            audio.addEventListener('play', function() {
                                audioManager.handlePlay(this);
                            });
                        });

                        // 监听来自父窗口的停止音频指令
                        window.addEventListener('message', function(event) {
                            if (event.data.type === 'stopAudio' &&
                                event.data.iframeId !== window.frameElement.id) {
                                audioManager.stopAll();
                            }
                        });
                    });
                </script>
            </body>
        </html>
    `;

          // 根据显示模式处理原代码
          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';
          }

          // 将iframe插入到code元素后面
          codeElement.parentNode.insertBefore(iframe, codeElement.nextSibling);

          // 初始调整iframe高度
          iframe.onload = function () {
            adjustIframeHeight(iframe);
            // 再次调整高度,以防有延迟加载的内容
            setTimeout(() => adjustIframeHeight(iframe), 500);
          };

          // 监听 iframe 内容变化
          if (iframe.contentWindow) {
            const resizeObserver = new ResizeObserver(() => adjustIframeHeight(iframe));
            resizeObserver.observe(iframe.contentWindow.document.body);
          }
        }
      }
    }
  }

  // 楼层初始化设置
  document.querySelector(`input[name="display-mode"][value="${displayMode}"]`).checked = true;
  document.getElementById('activation-mode').value = activationMode;
  document.getElementById('custom-start-floor').value = customStartFloor;
  document.getElementById('custom-end-floor').value = customEndFloor;
  document.getElementById('last-n-floors').value = customEndFloor;

  if (activationMode === 'custom') {
    document.getElementById('custom-floor-settings').style.display = 'block';
  } else if (activationMode === 'lastN') {
    document.getElementById('last-n-settings').style.display = 'block';
  }


  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 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');

        // 检查是否有变化或者没有注入的iframe
        if (currentContent !== lastMesTextContent || (isInjectionEnabled && !injectedIframe)) {
          lastMesTextContent = currentContent;
          if (isInjectionEnabled) {
            // 如果已经有iframe,先移除
            if (injectedIframe) {
              injectedIframe.remove();
            }
            // 重新注入
            injectHtmlCode(lastMesText);
          }
        }
      } else {
        // 如果没有code标签,但之前有内容,清除lastMesTextContent
        if (lastMesTextContent !== '') {
          lastMesTextContent = '';
          // 如果有之前注入的iframe,移除它
          const injectedIframe = lastMesText.querySelector('iframe');
          if (injectedIframe) {
            injectedIframe.remove();
          }
        }
      }
    }
  }

  // 监听DOM变化,处理动态加载的内容
  const observer = new MutationObserver((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;
          }
        }
      }
    }
  });

  observer.observe(document.body, { childList: true, subtree: true });

  // 边缘控制面板位置
  const savedPosition = GM_getValue('edgeControlsPosition', 'top-right');
  document.getElementById('edge-controls-position').value = savedPosition;
  updateEdgeControlsPosition(savedPosition);

  // 每2秒检查一次最后一个 mes_text 的变化
  setInterval(checkLastMesTextChange, 2000);

  // 在主脚本中添加全局音频管理
  function createGlobalAudioManager() {
    let currentPlayingIframeId = null;

    window.addEventListener('message', function (event) {
      if (event.data.type === 'audioPlay') {
        const newIframeId = event.data.iframeId;

        // 如果有其他iframe在播放音频,发送停止指令
        if (currentPlayingIframeId && currentPlayingIframeId !== newIframeId) {
          document.querySelectorAll('iframe').forEach(iframe => {
            iframe.contentWindow.postMessage({
              type: 'stopAudio',
              iframeId: newIframeId
            }, '*');
          });
        }

        currentPlayingIframeId = newIframeId;
      }
    });
  }

  // 初始化设置
  document.querySelector(`input[name="display-mode"][value="${displayMode}"]`).checked = true;

  // 初始化边缘控制面板状态
  updateEdgeControlsDisplay();

  // 在脚本初始化时调用全局音频控制
  createGlobalAudioManager();
})();