/** * Thing Extension - Popup 脚本 * 1. RPC 配置(存储到 chrome.storage.local) * 2. 资源嗅探列表查看与下载 */ const $ = (id) => document.getElementById(id) const DEFAULT_CONFIG = { serverUrl: 'http://127.0.0.1:16800', secret: '', interceptDownload: true, minSize: 0, excludeDomains: [], showNotifications: true, sniffEnabled: true, sniffTypes: ['video', 'audio', 'image', 'archive', 'torrent', 'installer'], sniffMaxItems: 200, sniffMinSize: 1024 * 100, sniffExcludeDomains: [] } function send(type, payload) { return new Promise((resolve) => { chrome.runtime.sendMessage({ type, ...payload }, resolve) }) } // ===== Tab 切换 ===== document.querySelectorAll('.tab').forEach(btn => { btn.addEventListener('click', () => { document.querySelectorAll('.tab').forEach(b => b.classList.remove('active')) document.querySelectorAll('.panel').forEach(p => p.classList.add('hidden')) btn.classList.add('active') $('panel-' + btn.dataset.tab).classList.remove('hidden') if (btn.dataset.tab === 'sniff') { refreshSniffList() } }) }) // ===== 状态指示 ===== function setStatus(state, text) { const dot = $('statusDot') const txt = $('statusText') dot.className = 'dot ' + (state === 'ok' ? 'ok' : state === 'fail' ? 'fail' : '') txt.textContent = text } // ===== 配置表单 ===== async function loadConfig() { return send('getConfig', {}) } function fillForm(config) { $('serverUrl').value = config.serverUrl || DEFAULT_CONFIG.serverUrl $('secret').value = config.secret || '' $('interceptDownload').checked = config.interceptDownload !== false $('sniffEnabled').checked = config.sniffEnabled !== false $('showNotifications').checked = config.showNotifications !== false $('minSize').value = config.minSize || 0 $('sniffMinSize').value = config.sniffMinSize ?? DEFAULT_CONFIG.sniffMinSize $('excludeDomains').value = (config.excludeDomains || []).join(',') } function readForm() { return { serverUrl: $('serverUrl').value.trim() || DEFAULT_CONFIG.serverUrl, secret: $('secret').value.trim(), interceptDownload: $('interceptDownload').checked, sniffEnabled: $('sniffEnabled').checked, showNotifications: $('showNotifications').checked, minSize: parseInt($('minSize').value, 10) || 0, sniffMinSize: parseInt($('sniffMinSize').value, 10) || 0, excludeDomains: $('excludeDomains').value .split(',') .map(s => s.trim()) .filter(Boolean) } } $('configForm').addEventListener('submit', async (e) => { e.preventDefault() const config = readForm() const res = await send('saveConfig', { config }) if (res && res.ok) { setStatus('', '已保存') setTimeout(() => setStatus('', '配置已保存'), 1500) } else { setStatus('fail', '保存失败:' + (res?.error || '未知错误')) } }) $('testBtn').addEventListener('click', async () => { setStatus('', '测试中...') const config = readForm() await send('saveConfig', { config }) const res = await send('testConnection', {}) if (res && res.ok) { setStatus('ok', '已连接 · Thing 下载引擎') } else { setStatus('fail', '连接失败:' + (res?.error || '未知错误')) } }) // ===== 嗅探列表 ===== let allSniffed = {} let currentSniffTab = 'video' // 当前选中的资源类型 tab // "其他"包含:archive/torrent/installer function getCategory(type) { if (type === 'video' || type === 'audio' || type === 'image') return type return 'other' } async function refreshSniffList() { allSniffed = await send('getSniffed', {}) updateTabCounts() renderSniffList() } function updateTabCounts() { const counts = { video: 0, audio: 0, image: 0, other: 0 } for (const tabId of Object.keys(allSniffed)) { const tabData = allSniffed[tabId] if (!tabData || !tabData.resources) continue for (const r of tabData.resources) { counts[getCategory(r.type)]++ } } $('count-video').textContent = counts.video $('count-audio').textContent = counts.audio $('count-image').textContent = counts.image $('count-other').textContent = counts.other } function getFilteredResources() { const all = [] for (const tabId of Object.keys(allSniffed)) { const tabData = allSniffed[tabId] if (!tabData || !tabData.resources) continue for (const r of tabData.resources) { if (getCategory(r.type) !== currentSniffTab) continue all.push({ ...r, tabTitle: tabData.title || r.tabTitle || '' }) } } // 按时间倒序 all.sort((a, b) => (b.lastSeen || 0) - (a.lastSeen || 0)) return all } function renderSniffList() { const list = $('sniffList') const items = getFilteredResources() if (items.length === 0) { list.innerHTML = '