(function () { const TALK_API_URL = 'https://mm.liushen.fun/api/echo/page'; const TALK_CACHE_KEY = 'liushenEchoCacheV2'; const TALK_CACHE_TIME_KEY = 'liushenEchoCacheTimeV2'; const TALK_CACHE_DURATION = 30 * 60 * 1000; const TALK_AVATAR = 'https://p.liiiu.cn/i/2025/03/13/67d2fc82d329c.webp'; const shuoshuoState = window.__liushenShuoshuoState || (window.__liushenShuoshuoState = { resizeHandler: null, afterRenderTimer: null, listenersBound: false }); function cleanupShuoshuo() { if (shuoshuoState.afterRenderTimer) { window.clearTimeout(shuoshuoState.afterRenderTimer); shuoshuoState.afterRenderTimer = null; } if (shuoshuoState.resizeHandler) { window.removeEventListener('resize', shuoshuoState.resizeHandler); shuoshuoState.resizeHandler = null; } } function renderTalks() { cleanupShuoshuo(); const talkContainer = document.querySelector('#talk'); if (!talkContainer) return; talkContainer.innerHTML = ''; const generateIconSVG = () => { return ''; }; const waterfall = (container) => { function getMargin(side, element) { const styles = window.getComputedStyle(element); return parseFloat(styles[`margin${side}`]) || 0; } function toPx(value) { return `${value}px`; } function getTop(element) { return parseFloat(element.style.top); } function getLeft(element) { return parseFloat(element.style.left); } function getWidth(element) { return element.clientWidth; } function getHeight(element) { return element.clientHeight; } function getBottom(element) { return getTop(element) + getHeight(element) + getMargin('Bottom', element); } function getRight(element) { return getLeft(element) + getWidth(element) + getMargin('Right', element); } function sortColumns(elements) { elements.sort((left, right) => { return getBottom(left) === getBottom(right) ? getLeft(right) - getLeft(left) : getBottom(right) - getBottom(left); }); } if (typeof container === 'string') { container = document.querySelector(container); } if (!container) return; const items = Array.from(container.children).map(item => { item.style.position = 'absolute'; return item; }); container.style.position = 'relative'; const columns = []; if (items.length) { items[0].style.top = '0px'; items[0].style.left = toPx(getMargin('Left', items[0])); columns.push(items[0]); } let index = 1; for (; index < items.length; index += 1) { const previous = items[index - 1]; const current = items[index]; const fits = getRight(previous) + getWidth(current) <= getWidth(container); if (!fits) break; current.style.top = previous.style.top; current.style.left = toPx(getRight(previous) + getMargin('Left', current)); columns.push(current); } for (; index < items.length; index += 1) { sortColumns(columns); const current = items[index]; const column = columns.pop(); current.style.top = toPx(getBottom(column) + getMargin('Top', current)); current.style.left = toPx(getLeft(column)); columns.push(current); } sortColumns(columns); const tallestColumn = columns[0]; container.style.height = tallestColumn ? toPx(getBottom(tallestColumn) + getMargin('Bottom', tallestColumn)) : '0px'; const currentWidth = getWidth(container); shuoshuoState.resizeHandler = () => { const currentContainer = document.querySelector('#talk'); if (!currentContainer || !document.body.contains(currentContainer)) { cleanupShuoshuo(); return; } if (getWidth(currentContainer) !== currentWidth) { waterfall(currentContainer); } }; window.addEventListener('resize', shuoshuoState.resizeHandler); }; const parseMaybeJson = (value) => { return value && typeof value === 'object' ? value : null; }; const getEchoExtension = (item) => { return parseMaybeJson(item?.extension); }; const getEchoExtensionType = (item) => { return getEchoExtension(item)?.type || ''; }; const getEchoExtensionPayload = (item) => { const extension = getEchoExtension(item); return extension?.payload || null; }; const getEchoImages = (item) => { if (!Array.isArray(item?.echo_files)) return []; return item.echo_files .map(entry => entry?.file || entry) .filter(file => { const category = String(file?.category || '').toLowerCase(); const contentType = String(file?.content_type || '').toLowerCase(); return category === 'image' || contentType.startsWith('image/'); }) .map(file => { let url = file?.url; if (!url) return null; // 如果是相对路径,补全为完整 URL if (url.startsWith('/')) { url = 'https://mm.liushen.fun' + url; } else if (!url.startsWith('http://') && !url.startsWith('https://')) { url = 'https://mm.liushen.fun/' + url; } return url; }) .filter(Boolean); }; const getEchoTags = (item) => { if (!Array.isArray(item?.tags) || !item.tags.length) return ['无标签']; return item.tags.map(tag => tag?.name || tag).filter(Boolean); }; const formatTime = (time) => { // 如果是 Unix 时间戳(秒),转换为毫秒 const timestamp = time < 10000000000 ? time * 1000 : time; const date = new Date(timestamp); const pad = value => String(value).padStart(2, '0'); return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ${pad(date.getHours())}:${pad(date.getMinutes())}`; }; const renderTextContent = (content) => { return (content || '') .replace(/\[(.*?)\]\((.*?)\)/g, '@$1') .replace(/- \[ \]/g, '[]') .replace(/- \[x\]/gi, '[x]') .replace(/\n/g, '
'); }; const buildImageHtml = (images) => { if (!images.length) return ''; const imageLinks = images.map(url => { const safeUrl = `${url}?fmt=webp&q=75`; return ``; }).join(''); return `
${imageLinks}
`; }; const getGithubTitle = (repoUrl) => { if (!repoUrl) return ''; const match = repoUrl.match(/^https?:\/\/github\.com\/[^/]+\/([^/?#]+)/i); if (match) return match[1]; try { const parts = new URL(repoUrl).pathname.split('/').filter(Boolean); return parts.pop() || repoUrl; } catch (error) { return repoUrl; } }; const buildExternalHtml = (type, payload) => { if (!payload) return ''; let siteUrl = ''; let title = ''; let background = 'https://p.liiiu.cn/i/2024/07/27/66a4632bbf06e.webp'; if (type === 'WEBSITE') { siteUrl = payload.site || payload.url || ''; title = payload.title || siteUrl; } if (type === 'GITHUBPROJ') { siteUrl = payload.repoUrl || payload.url || ''; title = payload.title || getGithubTitle(siteUrl); background = 'https://p.liiiu.cn/i/2024/07/27/66a461a3098aa.webp'; } if (!siteUrl) return ''; return ` `; }; const getMusicInfo = (payload) => { const link = payload?.url; if (!link) return null; let server = ''; if (link.includes('music.163.com')) server = 'netease'; if (link.includes('y.qq.com')) server = 'tencent'; const idMatch = link.match(/id=(\d+)/); if (!server || !idMatch) return null; return { server, id: idMatch[1] }; }; const buildMusicHtml = (payload) => { const music = getMusicInfo(payload); if (!music) return ''; return ``; }; const getYoutubeVideoId = (value) => { if (!value) return ''; if (/^[a-zA-Z0-9_-]{11}$/.test(value)) return value; try { const url = new URL(value); if (url.hostname.includes('youtu.be')) return url.pathname.replace('/', ''); if (url.hostname.includes('youtube.com')) { return url.searchParams.get('v') || url.pathname.split('/').filter(Boolean).pop() || ''; } } catch (error) { return ''; } return ''; }; const buildVideoHtml = (payload) => { const rawValue = payload?.videoId || payload?.url || ''; if (!rawValue) return ''; let embedUrl = ''; if (/^BV[0-9A-Za-z]+$/i.test(rawValue)) { embedUrl = `https://www.bilibili.com/blackboard/html5mobileplayer.html?bvid=${rawValue}&as_wide=1&high_quality=1&danmaku=0`; } else { const youtubeId = getYoutubeVideoId(rawValue); if (youtubeId) { embedUrl = `https://www.youtube.com/embed/${youtubeId}`; } } if (!embedUrl) return ''; return `
`; }; const normalizeTalk = (item) => { const extensionType = getEchoExtensionType(item); const extensionPayload = getEchoExtensionPayload(item); const textContent = item?.content || ''; const images = getEchoImages(item); let content = `
${renderTextContent(textContent)}
`; content += buildImageHtml(images); if (extensionType === 'WEBSITE' || extensionType === 'GITHUBPROJ') { content += buildExternalHtml(extensionType, extensionPayload); } if (extensionType === 'MUSIC') { content += buildMusicHtml(extensionPayload); } if (extensionType === 'VIDEO') { content += buildVideoHtml(extensionPayload); } return { content, user: item?.username || '匿名', avatar: TALK_AVATAR, date: formatTime(item?.created_at), tags: getEchoTags(item), quoteText: textContent }; }; const generateTalkElement = (item) => { const talkItem = document.createElement('div'); talkItem.className = 'talk_item'; const talkMeta = document.createElement('div'); talkMeta.className = 'talk_meta'; const avatar = document.createElement('img'); avatar.className = 'no-lightbox avatar'; avatar.src = item.avatar; const info = document.createElement('div'); info.className = 'info'; const nick = document.createElement('span'); nick.className = 'talk_nick'; nick.innerHTML = `${item.user} ${generateIconSVG()}`; const date = document.createElement('span'); date.className = 'talk_date'; date.textContent = item.date; info.appendChild(nick); info.appendChild(date); talkMeta.appendChild(avatar); talkMeta.appendChild(info); const talkContent = document.createElement('div'); talkContent.className = 'talk_content'; talkContent.innerHTML = item.content; const talkBottom = document.createElement('div'); talkBottom.className = 'talk_bottom'; const tags = document.createElement('div'); const tag = document.createElement('span'); tag.className = 'talk_tag'; tag.textContent = `# ${item.tags.join(' / ')}`; tags.appendChild(tag); const commentLink = document.createElement('a'); commentLink.href = 'javascript:;'; commentLink.addEventListener('click', () => goComment(item.quoteText)); const icon = document.createElement('span'); icon.className = 'icon'; icon.innerHTML = ''; commentLink.appendChild(icon); talkBottom.appendChild(tags); talkBottom.appendChild(commentLink); talkItem.appendChild(talkMeta); talkItem.appendChild(talkContent); talkItem.appendChild(talkBottom); return talkItem; }; const goComment = (text) => { const textarea = document.querySelector('.atk-textarea'); if (!textarea) return; textarea.value = `> ${text || ''}\n\n`; textarea.focus(); if (window.btf?.snackbarShow) { btf.snackbarShow('已为您引用该说说,删除空格效果更佳'); } }; const afterRender = () => { waterfall('#talk'); if (window.btf?.loadLightbox) { btf.loadLightbox(document.querySelectorAll('#talk img:not(.no-lightbox)')); } if (window.lazyLoadInstance?.update) { lazyLoadInstance.update(); } }; const renderTalksList = (list) => { list.map(normalizeTalk).forEach(item => talkContainer.appendChild(generateTalkElement(item))); afterRender(); const media = talkContainer.querySelectorAll('img, iframe, meting-js'); media.forEach(element => { element.addEventListener('load', afterRender, { once: true }); }); shuoshuoState.afterRenderTimer = window.setTimeout(afterRender, 300); }; const fetchTalks = () => { const cachedData = localStorage.getItem(TALK_CACHE_KEY); const cachedTime = Number(localStorage.getItem(TALK_CACHE_TIME_KEY)); const now = Date.now(); if (cachedData && cachedTime && now - cachedTime < TALK_CACHE_DURATION) { renderTalksList(JSON.parse(cachedData)); return; } fetch(TALK_API_URL, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ page: 1, pageSize: 30, search: '' }) }) .then(response => response.json()) .then(data => { if (data?.code !== 1 || !Array.isArray(data?.data?.items)) { console.warn('Unexpected API response format:', data); renderTalksList([]); return; } localStorage.setItem(TALK_CACHE_KEY, JSON.stringify(data.data.items)); localStorage.setItem(TALK_CACHE_TIME_KEY, now.toString()); renderTalksList(data.data.items); }) .catch(error => console.error('Error fetching data:', error)); }; fetchTalks(); } function initShuoshuoPage() { renderTalks(); } window.initShuoshuoPage = initShuoshuoPage; if (!shuoshuoState.listenersBound) { document.addEventListener('pjax:send', cleanupShuoshuo); document.addEventListener('pjax:complete', initShuoshuoPage); shuoshuoState.listenersBound = true; } initShuoshuoPage(); })();