const state = { uid: '7716940453', mode: 'any', query: '', terms: [], count: 50 } const wbOut = document.getElementById('wb-debug-output') const wbBtnCopy = document.getElementById('wb-btn-copy') const wbBtnRefresh = document.getElementById('wb-btn-refresh') const wbBtnClear = document.getElementById('wb-btn-clear') const wbRender = lines => { if (!wbOut) return wbOut.textContent = lines.join('\n') wbOut.scrollTop = wbOut.scrollHeight } const wbLoad = async () => { try { const res = await fetch('/api/debug/logs?limit=5000&channel=weibo') const data = await res.json() if (data.ok) wbRender(data.lines) } catch {} } const ts = () => new Date().toISOString() const logLocal = msg => { if (!wbOut) return const line = `${ts()} ${msg}` const current = wbOut.textContent || '' wbOut.textContent = current ? current + "\n" + line : line wbOut.scrollTop = wbOut.scrollHeight } const renderPosts = rows => { const box = el('fetch-results') box.innerHTML = '' for (const r of rows) { const card = document.createElement('div') card.className = 'card' const meta = document.createElement('div') meta.className = 'meta' meta.textContent = `${r.screen_name || ''} · ${r.created_at || ''}` const text = document.createElement('div') text.innerHTML = highlight(r.text || '', state.terms) const link = document.createElement('a') link.href = (r.retweeted_url || r.url || '#') link.target = '_blank' link.textContent = '打开原文' if (r.retweeted_text) { const rt = document.createElement('div') rt.className = 'meta' rt.innerHTML = `↪ 原博 ${r.retweeted_screen_name || ''}: ` + highlight(r.retweeted_text || '', state.terms) card.appendChild(rt) } const stat = document.createElement('div') stat.className = 'meta' stat.textContent = `转发:${r.reposts_count} 评论:${r.comments_count} 赞:${r.attitudes_count}` card.appendChild(meta) card.appendChild(text) card.appendChild(link) card.appendChild(stat) box.appendChild(card) } } const setResultsMeta = text => { const m = document.getElementById('results-meta') if (m) m.textContent = String(text || '') } const fetchLatest = async () => { const uid = el('input-uid').value.trim() logLocal(`frontend.event {"source":"weibo","action":"fetch.click","uid":"${uid}"}`) try { await fetch('/api/debug/event', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ source: 'weibo', action: 'fetch.click', uid }) }) } catch {} wbLoad() const statusBox = document.getElementById('status') if (statusBox) { statusBox.style.display = ''; statusBox.textContent = `正在拉取最近${state.count}条...` } const res = await fetch(`/api/weibo/${uid}/fetch?count=${encodeURIComponent(state.count || 50)}`) const data = await res.json() if (!data.ok) { logLocal(`weibo.fetch.error ${JSON.stringify({ error: data.error })}`) if (statusBox) statusBox.textContent = (data.hint ? data.hint + '。' : '') + (data.error || '拉取失败') alert(`拉取失败:${String(data.error || 'unknown')}`) } else { logLocal(`weibo.fetch.done ${JSON.stringify({ uid, fetched: data.fetched })}`) if (statusBox) { statusBox.style.display = 'none'; statusBox.textContent = '' } alert(`本次拉取:${data.fetched || 0} 条`) const latestBox = document.getElementById('latest-data-time'); if (latestBox) latestBox.value = String(data.latest_time || '') const sourceMap = { devtools: 'DevTools', settings: '设置Cookie', anonymous: '匿名Cookie', '': '未知' } const srcLabel = sourceMap[String(data.source || '')] || '未知' const rows = Array.isArray(data.rows) ? data.rows : [] renderPosts(rows) setResultsMeta(`数据来源:${srcLabel} · 仅显示本次拉取结果 · 当前 ${rows.length} 条`) } wbLoad() return data } const renderAll = async (q = '', mode = 'any') => { const uid = el('input-uid').value.trim() state.mode = mode state.query = q state.terms = q ? q.split(/\s+/).filter(Boolean) : [] logLocal(`frontend.event ${JSON.stringify({ source:'weibo', action:'kw.search', uid, mode, terms: state.terms })}`) const res = await fetch(`/api/weibo/search?uid=${encodeURIComponent(uid)}&q=${encodeURIComponent(q)}&mode=${encodeURIComponent(mode)}&limit=50`) const data = await res.json() if (data.ok) { renderPosts(data.rows); logLocal(`frontend.event ${JSON.stringify({ source:'weibo', action:'kw.search.done', count: (data.rows||[]).length })}`); return data.rows || [] } return [] } const matches = (text, terms, mode) => { if (!terms || terms.length === 0) return true const checks = terms.map(t => new RegExp(t.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'i').test(text)) return mode === 'all' ? checks.every(Boolean) : checks.some(Boolean) } const loadArchiveToPanel = async () => { const uid = el('input-uid').value.trim() const start = el('arch-week-start').value const end = el('arch-week-end').value const res = await fetch(`/api/archives/get?uid=${encodeURIComponent(uid)}&start=${encodeURIComponent(start)}&end=${encodeURIComponent(end)}`) const data = await res.json() if (data.ok) el('archive-results').innerHTML = data.html || '' } const searchArchive = async () => { const q = el('arch-query').value.trim() const mode = el('arch-mode').value const terms = q ? q.split(/\s+/).filter(Boolean) : [] const cards = Array.from(document.querySelectorAll('#archive-results .card')) if (cards.length === 0) return cards.forEach(card => { const text = card.textContent || '' const show = matches(text, terms, mode) card.style.display = show ? '' : 'none' if (show) card.innerHTML = highlight(card.innerHTML, terms) }) } const openSettings = () => { el('settings-modal').classList.remove('hidden') } const closeSettings = () => { const closeBtn = el('btn-close-settings') if (closeBtn) { closeBtn.classList.add('warning') setTimeout(() => { closeBtn.classList.remove('warning') el('settings-modal').classList.add('hidden') }, 300) } else { el('settings-modal').classList.add('hidden') } } const saveSettings = async () => { const saveBtn = el('btn-save-settings') // 添加保存动画效果 if (saveBtn) { saveBtn.classList.add('saving') saveBtn.disabled = true saveBtn.textContent = '保存中...' } const uid = el('settings-uid').value.trim() const countVal = parseInt((document.getElementById('settings-count').value || '50'), 10) const kwRaw = el('settings-keywords').value.trim() const keywords = kwRaw.split('|').map(s => s.trim()).filter(Boolean) const body = { users: [{ uid, screen_name: '' }], keywords, headers: {}, count: countVal } logLocal(`frontend.event ${JSON.stringify({ source: 'weibo', action: 'settings.save', uid, keywordsCount: keywords.length })}`) try { await fetch('/api/debug/event', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ source: 'weibo', action: 'settings.save', uid, keywordsCount: keywords.length }) }) } catch {} await fetch('/api/settings', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body) }) // 恢复按钮状态 if (saveBtn) { setTimeout(() => { saveBtn.classList.remove('saving') saveBtn.disabled = false saveBtn.textContent = '保存' // 添加成功确认效果 saveBtn.style.background = 'linear-gradient(135deg, #00ff00, #00cc00)' setTimeout(() => { saveBtn.style.background = 'linear-gradient(135deg, var(--primary), var(--secondary))' closeSettings() }, 500) }, 1000) } else { closeSettings() } wbLoad() state.count = countVal const b = document.getElementById('btn-fetch') if (b) b.textContent = `拉取最近${state.count}条` } const loadSettings = async () => { try { const res = await fetch('/api/settings') const data = await res.json() const cfg = (data && data.file) || {} const uid = (cfg.users && cfg.users[0] && cfg.users[0].uid) ? String(cfg.users[0].uid) : (state.uid || '') const count = parseInt(String(cfg.count || state.count), 10) const kw = Array.isArray(cfg.keywords) ? cfg.keywords.join('|') : '' const iu = document.getElementById('input-uid'); if (iu) iu.value = uid const su = document.getElementById('settings-uid'); if (su) su.value = uid const sc = document.getElementById('settings-count'); if (sc) sc.value = String(count) const sk = document.getElementById('settings-keywords'); if (sk) sk.value = kw state.uid = uid state.count = count const b = document.getElementById('btn-fetch'); if (b) b.textContent = `拉取最近${state.count}条` renderKeywordChips(Array.isArray(cfg.keywords) ? cfg.keywords : []) } catch {} } const truncateLabel = (s, n = 8) => { const str = String(s || '') return str.length > n ? (str.slice(0, n) + '...') : str } const renderKeywordChips = (keywords) => { try { const panelArchive = document.getElementById('panel-archive') let archHolder = document.getElementById('arch-chips') if (!archHolder && panelArchive) { archHolder = document.createElement('div'); archHolder.id = 'arch-chips'; archHolder.className = 'chips'; panelArchive.insertBefore(archHolder, panelArchive.firstChild) } if (archHolder) archHolder.innerHTML = '' const groups = (keywords || []).map(k => String(k || '').trim()).filter(Boolean) for (const g of groups) { const btn = document.createElement('button'); btn.className = 'chip'; btn.dataset.q = g; btn.textContent = truncateLabel(g, 8) btn.style.margin = '0 6px 6px 0' btn.addEventListener('click', () => { const iq = document.getElementById('arch-query'); if (iq) iq.value = g; searchArchive() }) if (archHolder) archHolder.appendChild(btn) } // ensure no stray chips remain in fetch panel Array.from(document.querySelectorAll('#panel-fetch .chip')).forEach(ch => ch.remove()) } catch {} } // 创建粒子效果 function createParticles() { const container = document.getElementById('particles-container') if (!container) return for (let i = 0; i < 30; i++) { const particle = document.createElement('div') particle.className = 'particle' particle.style.left = Math.random() * 100 + '%' particle.style.animationDelay = Math.random() * 6 + 's' particle.style.animationDuration = (Math.random() * 3 + 4) + 's' container.appendChild(particle) } } // 添加霓虹灯效果到输入框 function addNeonEffects() { const inputs = document.querySelectorAll('input[type="text"], input[type="date"], select') inputs.forEach(input => { input.classList.add('neon-input') // 添加焦点效果 input.addEventListener('focus', function() { this.style.boxShadow = '0 0 20px rgba(0, 255, 255, 0.5), 0 0 40px rgba(0, 255, 255, 0.3)' }) input.addEventListener('blur', function() { this.style.boxShadow = '0 0 10px rgba(255, 20, 147, 0.3)' }) }) } // 添加按钮特殊效果 function enhanceButtons() { const specialButtons = ['btn-fetch', 'btn-save-week', 'arch-search'] specialButtons.forEach(id => { const btn = document.getElementById(id) if (btn) { btn.classList.add('btn-special') } }) } document.addEventListener('DOMContentLoaded', async () => { try { // 初始化视觉效果 createParticles() addNeonEffects() enhanceButtons() logLocal('frontend.event {"source":"weibo","action":"dom.ready"}') const bf = el('btn-fetch'); if (bf) bf.addEventListener('click', async () => { await fetchLatest() }) const bs = el('btn-search'); if (bs) bs.addEventListener('click', searchPosts) const bset = el('btn-settings'); if (bset) bset.addEventListener('click', openSettings) const bclose = el('btn-close-settings'); if (bclose) bclose.addEventListener('click', closeSettings) const bsave = el('btn-save-settings'); if (bsave) bsave.addEventListener('click', saveSettings) const dStart = document.getElementById('week-start') const dEnd = document.getElementById('week-end') const btnSaveWeek = document.getElementById('btn-save-week') const btnWeekPrev = document.getElementById('btn-week-prev') const btnWeekCurrent = document.getElementById('btn-week-current') const btnWeekNext = document.getElementById('btn-week-next') const fmt = d => { const y = d.getFullYear() const m = String(d.getMonth() + 1).padStart(2, '0') const day = String(d.getDate()).padStart(2, '0') return `${y}-${m}-${day}` } const weekRangeFromDate = anchor => { const d = new Date(anchor) const dow = d.getDay() const shift = dow === 0 ? -6 : 1 - dow const start = new Date(d.getFullYear(), d.getMonth(), d.getDate() + shift) const end = new Date(start.getFullYear(), start.getMonth(), start.getDate() + 6) return { start, end } } const setWeek = anchor => { const r = weekRangeFromDate(anchor) dStart.value = fmt(r.start) dEnd.value = fmt(r.end) } const saveArchive = async () => { const uid = el('input-uid').value.trim() const content = el('fetch-results').innerHTML const header = `
` const html = `