import { eventSource, event_types } from '/script.js'; import { extension_settings } from '/scripts/extensions.js'; import { extensionName } from '../../utils/settings.js'; const settings = { sandboxMode: false, useBlob: false, wrapperIframe: true, renderEnabled: true }; const winMap = new Map(); let lastHeights = new WeakMap(); const blobUrls = new WeakMap(); const hashToBlobUrl = new Map(); const blobLRU = []; const BLOB_CACHE_LIMIT = 32; function generateUniqueId() { return `amily2-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`; } function shouldRenderContentByBlock(codeBlock) { if (!codeBlock) return false; const content = (codeBlock.textContent || '').trim(); if (!content) return false; return /^\s*>> 0).toString(16); } function buildResourceHints(html) { const urls = Array.from(new Set((html.match(/https?:\/\/[^"'()\s]+/gi) || []).map(u => { try { return new URL(u).origin } catch { return null } }).filter(Boolean))); let hints = ""; const maxHosts = 6; for (let i = 0; i < Math.min(urls.length, maxHosts); i++) { const origin = urls[i]; hints += ``; hints += ``; } let preload = ""; const font = (html.match(/https?:\/\/[^"'()\s]+\.(?:woff2|woff|ttf|otf)/i) || [])[0]; if (font) { const type = font.endsWith(".woff2") ? "font/woff2" : font.endsWith(".woff") ? "font/woff" : font.endsWith(".ttf") ? "font/ttf" : "font/otf"; preload += ``; } const css = (html.match(/https?:\/\/[^"'()\s]+\.css/i) || [])[0]; if (css) { preload += ``; } const img = (html.match(/https?:\/\/[^"'()\s]+\.(?:png|jpg|jpeg|webp|gif|svg)/i) || [])[0]; if (img) { preload += ``; } return hints + preload; } function iframeClientScript() { return ` (function(){ function measureVisibleHeight(){ try{ var doc = document; var target = doc.querySelector('.calendar-wrapper') || doc.body; if(!target) return 0; var minTop = Infinity, maxBottom = 0; var addRect = function(el){ try{ var r = el.getBoundingClientRect(); if(r && r.height > 0){ if(minTop > r.top) minTop = r.top; if(maxBottom < r.bottom) maxBottom = r.bottom; } }catch(e){} }; addRect(target); var children = target.children || []; for(var i=0;i 0 ? Math.ceil(maxBottom - Math.min(minTop, 0)) : (target.scrollHeight || 0); }catch(e){ return (document.body && document.body.scrollHeight) || 0; } } function post(m){ try{ parent.postMessage(m,'*') }catch(e){} } var rafPending=false, lastH=0; var HYSTERESIS = 2; function send(force){ if(rafPending && !force) return; rafPending = true; requestAnimationFrame(function(){ rafPending = false; var h = measureVisibleHeight(); if(force || Math.abs(h - lastH) >= HYSTERESIS){ lastH = h; post({height:h, force:!!force}); } }); } try{ send(true) }catch(e){} document.addEventListener('DOMContentLoaded', function(){ send(true) }, {once:true}); window.addEventListener('load', function(){ send(true) }, {once:true}); try{ if(document.fonts){ document.fonts.ready.then(function(){ send(true) }).catch(function(){}); if(document.fonts.addEventListener){ document.fonts.addEventListener('loadingdone', function(){ send(true) }); document.fonts.addEventListener('loadingerror', function(){ send(true) }); } } }catch(e){} ['transitionend','animationend'].forEach(function(evt){ document.addEventListener(evt, function(){ send(false) }, {passive:true, capture:true}); }); try{ var root = document.querySelector('.calendar-wrapper') || document.body || document.documentElement; var ro = new ResizeObserver(function(){ send(false) }); ro.observe(root); }catch(e){ try{ var rootMO = document.querySelector('.calendar-wrapper') || document.body || document.documentElement; new MutationObserver(function(){ send(false) }) .observe(rootMO, {childList:true, subtree:true, attributes:true, characterData:true}); }catch(e){} window.addEventListener('resize', function(){ send(false) }, {passive:true}); } window.addEventListener('message', function(e){ var d = e && e.data || {}; if(d && d.type === 'probe') setTimeout(function(){ send(true) }, 10); }); })();`; } function buildWrappedHtml(html) { const origin = (typeof location !== 'undefined' && location.origin) ? location.origin : ''; const baseTag = settings && settings.useBlob ? `` : ""; const headHints = buildResourceHints(html); const vhFix = ``; const apiScript = ` `; const injectionBlock = ` ${baseTag} ${headHints} ${vhFix} ${apiScript} `; const isFullHtml = //i.test(html); if (isFullHtml) { if (html.includes('')) { return html.replace('', `${injectionBlock}`); } else if (html.includes('${injectionBlock}${injectionBlock}${html}`; } return ` ${injectionBlock} ${html}`; } function getOrCreateWrapper(preEl) { let wrapper = preEl.previousElementSibling; if (!wrapper || !wrapper.classList.contains('amily2-iframe-wrapper')) { wrapper = document.createElement('div'); wrapper.className = 'amily2-iframe-wrapper'; wrapper.style.cssText = 'margin:0;'; preEl.parentNode.insertBefore(wrapper, preEl); } return wrapper; } function registerIframeMapping(iframe, wrapper) { const tryMap = () => { try { if (iframe && iframe.contentWindow) { winMap.set(iframe.contentWindow, { iframe, wrapper }); return true; } } catch (e) { } return false; }; if (tryMap()) return; let tries = 0; const t = setInterval(() => { tries++; if (tryMap() || tries > 20) clearInterval(t); }, 25); } function handleIframeMessage(event) { const data = event.data || {}; let rec = winMap.get(event.source); if (!rec || !rec.iframe) { const iframes = document.querySelectorAll('iframe.amily2-iframe'); for (const iframe of iframes) { if (iframe.contentWindow === event.source) { rec = { iframe, wrapper: iframe.parentElement }; winMap.set(event.source, rec); break; } } } if (rec && rec.iframe && typeof data.height === 'number') { const next = Math.max(0, Number(data.height) || 0); if (next < 1) return; const prev = lastHeights.get(rec.iframe) || 0; if (!data.force && Math.abs(next - prev) < 1) return; lastHeights.set(rec.iframe, next); requestAnimationFrame(() => { rec.iframe.style.height = `${next}px`; }); } } function setIframeBlobHTML(iframe, fullHTML, codeHash) { const existing = hashToBlobUrl.get(codeHash); if (existing) { iframe.src = existing; blobUrls.set(iframe, existing); return; } const blob = new Blob([fullHTML], { type: 'text/html' }); const url = URL.createObjectURL(blob); iframe.src = url; blobUrls.set(iframe, url); hashToBlobUrl.set(codeHash, url); blobLRU.push(codeHash); while (blobLRU.length > BLOB_CACHE_LIMIT) { const old = blobLRU.shift(); const u = hashToBlobUrl.get(old); hashToBlobUrl.delete(old); try { URL.revokeObjectURL(u) } catch (e) { } } } function releaseIframeBlob(iframe) { try { const url = blobUrls.get(iframe); if (url) URL.revokeObjectURL(url); blobUrls.delete(iframe); } catch (e) { } } function renderHtmlInIframe(htmlContent, container, preElement) { try { const originalHash = djb2(htmlContent); const iframe = document.createElement('iframe'); iframe.id = generateUniqueId(); iframe.className = 'amily2-iframe'; iframe.style.cssText = 'width:100%;border:none;background:transparent;overflow:hidden;height:0;margin:0;padding:0;display:block;contain:layout paint style;will-change:height;min-height:50px'; iframe.setAttribute('frameborder', '0'); iframe.setAttribute('scrolling', 'no'); iframe.loading = 'eager'; if (settings.sandboxMode) { iframe.setAttribute('sandbox', 'allow-scripts allow-modals'); } else { iframe.setAttribute('sandbox', 'allow-scripts allow-same-origin allow-forms allow-modals allow-popups'); } const wrapper = getOrCreateWrapper(preElement); wrapper.querySelectorAll('.amily2-iframe').forEach(old => { try { old.src = 'about:blank'; } catch (e) { } releaseIframeBlob(old); old.remove(); }); const codeHash = djb2(htmlContent); const full = buildWrappedHtml(htmlContent); if (settings.useBlob) { setIframeBlobHTML(iframe, full, codeHash); } else { iframe.srcdoc = full; } wrapper.appendChild(iframe); preElement.classList.remove('xb-show'); preElement.style.display = 'none'; registerIframeMapping(iframe, wrapper); try { iframe.contentWindow?.postMessage({ type: 'probe' }, '*'); } catch (e) { } preElement.dataset.xbFinal = 'true'; preElement.dataset.xbHash = originalHash; return iframe; } catch (err) { return null; } } function processCodeBlocks(messageElement) { if (extension_settings[extensionName].render_enabled === false) return; try { const codeBlocks = messageElement.querySelectorAll('pre > code'); codeBlocks.forEach(codeBlock => { const preElement = codeBlock.parentElement; const should = shouldRenderContentByBlock(codeBlock); const html = codeBlock.textContent || ''; const hash = djb2(html); const isFinal = preElement.dataset.xbFinal === 'true'; const same = preElement.dataset.xbHash === hash; if (isFinal && same) return; if (should) { renderHtmlInIframe(html, preElement.parentNode, preElement); } else { preElement.classList.add('xb-show'); preElement.removeAttribute('data-xbfinal'); preElement.removeAttribute('data-xbhash'); preElement.style.display = ''; } preElement.dataset.xiaobaixBound = 'true'; }); } catch (err) { console.error('[Amily2-Renderer] Error during processCodeBlocks:', err); } } function processMessageById(messageId) { const messageElement = document.querySelector(`div.mes[mesid="${messageId}"] .mes_text`); if (!messageElement) return; processCodeBlocks(messageElement); } export function initializeRenderer() { const handleMessage = (data) => { const messageId = typeof data === 'object' ? data.messageId : data; if (messageId == null) return; console.log('[Amily2-Renderer] 处理消息渲染:', messageId); setTimeout(() => processMessageById(messageId), 50); }; eventSource.on(event_types.MESSAGE_RECEIVED, handleMessage); eventSource.on(event_types.MESSAGE_UPDATED, handleMessage); eventSource.on(event_types.MESSAGE_SWIPED, handleMessage); eventSource.on(event_types.MESSAGE_EDITED, handleMessage); eventSource.on(event_types.USER_MESSAGE_RENDERED, handleMessage); eventSource.on(event_types.CHARACTER_MESSAGE_RENDERED, handleMessage); eventSource.on(event_types.IMPERSONATE_READY, handleMessage); eventSource.on(event_types.CHAT_CHANGED, () => { console.log('[Amily2-Renderer] 聊天已切换,重新渲染所有 iframe'); setTimeout(renderAllIframes, 100); }); window.addEventListener('message', handleIframeMessage); console.log('[Amily2-Renderer] 渲染器已初始化,监听事件: MESSAGE_RECEIVED, MESSAGE_UPDATED, MESSAGE_SWIPED, MESSAGE_EDITED, USER_MESSAGE_RENDERED, CHARACTER_MESSAGE_RENDERED, IMPERSONATE_READY'); } export function renderAllIframes() { const messages = document.querySelectorAll('.mes'); messages.forEach(message => { const messageId = message.getAttribute('mesid'); if (messageId) { processMessageById(messageId); } }); } export function clearAllIframes() { const iframes = document.querySelectorAll('.amily2-iframe'); iframes.forEach(iframe => { const wrapper = iframe.parentElement; if (wrapper && wrapper.classList.contains('amily2-iframe-wrapper')) { const preElement = wrapper.nextElementSibling; if (preElement && preElement.tagName === 'PRE') { preElement.classList.add('xb-show'); preElement.style.display = ''; } wrapper.remove(); } }); }