// --- LifeScrobbler background script --- // Requires shared_settings.js for DEFAULT_SETTINGS and helpers. const activeTabs = new Map(); // tabId -> timeout const currentURLs = new Map(); // tabId -> current URL const pausedTabs = new Set(); // tabId -> paused state // --------------------------------------------------------------------------- // Utility helpers // --------------------------------------------------------------------------- async function getSettings() { return new Promise(resolve => { browser.storage.local.get(DEFAULT_SETTINGS, resolve); }); } function updateIcon(tabId, state) { const icons = { paused: "icons/icon-paused.png", wait: "icons/icon-wait.png", scrobbled: "icons/icon-scrobbled.png" }; const path = icons[state] || "icons/icon-32.png"; browser.browserAction.setIcon({ tabId, path }); } // Enhanced blacklist matcher with wildcard and regex support function isBlacklisted(url, blacklist) { try { const { hostname, href } = new URL(url); const extraBlacklist = ["moz-extension://"]; // always skipped const combined = [...blacklist, ...extraBlacklist]; return combined.some(entry => { if (!entry) return false; entry = entry.trim(); // Regex support if (entry.startsWith("/") && entry.endsWith("/")) { try { const re = new RegExp(entry.slice(1, -1)); return re.test(href); } catch { return false; } } // Wildcard domain support if (entry.startsWith("*.")) { const domain = entry.slice(2).toLowerCase(); return ( hostname.toLowerCase().endsWith("." + domain) || hostname.toLowerCase() === domain ); } // Substring match return href.includes(entry); }); } catch (e) { console.warn("isBlacklisted error:", e); return false; } } // Per-site delay override (e.g., amazon.com every 1 second) function getDelayForUrl(url, settings) { const hostname = new URL(url).hostname; const overrides = settings.siteDelays || {}; for (const [pattern, delay] of Object.entries(overrides)) { if (hostname.includes(pattern)) { return delay; } } return settings.delaySeconds; } // --------------------------------------------------------------------------- // Scrobbling functions // --------------------------------------------------------------------------- async function scrobbleStart(url, baseUrl, referrer = null) { let endpoint = `${baseUrl}/?source=Firefox&scrobble_url=${encodeURIComponent(url)}`; if (referrer) endpoint += `&referrer=${encodeURIComponent(referrer)}`; try { await fetch(endpoint, { method: "GET" }); console.log("LifeScrobbler: started", url, referrer ? `(ref: ${referrer})` : ""); } catch (err) { console.error("LifeScrobbler: start failed", url, err); } } async function scrobbleStop(url, baseUrl) { if (!url) return; const endpoint = `${baseUrl}/?action=stop&scrobble_url=${encodeURIComponent(url)}`; try { await fetch(endpoint, { method: "GET" }); console.log("LifeScrobbler: stopped", url); } catch (err) { console.error("LifeScrobbler: stop failed", url, err); } } // --------------------------------------------------------------------------- // Event handlers // --------------------------------------------------------------------------- // Main listener: when navigating to a new URL browser.webNavigation.onCommitted.addListener(async (details) => { if (details.frameId !== 0) return; // only main frame const tabId = details.tabId; const newUrl = details.url; const prevUrl = currentURLs.get(tabId) || null; currentURLs.set(tabId, newUrl); const settings = await getSettings(); const { paused, blacklist, scrobbleBaseUrl } = settings; // Skip if paused or blacklisted if (paused || pausedTabs.has(tabId) || isBlacklisted(newUrl, blacklist)) return; // Stop previous URL first (if valid) if (prevUrl && !isBlacklisted(prevUrl, blacklist)) { scrobbleStop(prevUrl, scrobbleBaseUrl); } // Schedule start scrobble const siteDelay = getDelayForUrl(newUrl, settings); if (activeTabs.has(tabId)) clearTimeout(activeTabs.get(tabId)); const timeout = setTimeout(() => { scrobbleStart(newUrl, scrobbleBaseUrl, prevUrl); updateIcon(tabId, "scrobbled"); setTimeout(() => updateIcon(tabId, "wait"), 3000); }, siteDelay * 1000); activeTabs.set(tabId, timeout); }); // Handle tab close (send stop) browser.tabs.onRemoved.addListener(async (tabId) => { const url = currentURLs.get(tabId); if (!url) return; const settings = await getSettings(); const { blacklist, scrobbleBaseUrl } = settings; if (isBlacklisted(url, blacklist)) return; scrobbleStop(url, scrobbleBaseUrl); activeTabs.delete(tabId); currentURLs.delete(tabId); }); // Toggle pause via toolbar icon click browser.browserAction.onClicked.addListener(async (tab) => { const settings = await getSettings(); const tabId = tab.id; if (pausedTabs.has(tabId)) { pausedTabs.delete(tabId); updateIcon(tabId, "wait"); } else { pausedTabs.add(tabId); updateIcon(tabId, "paused"); } // Persist pause state await browser.storage.local.set({ pausedTabs: Array.from(pausedTabs) }); }); // Restore paused tabs on startup (async function restorePausedTabs() { const data = await browser.storage.local.get({ pausedTabs: [] }); for (const tabId of data.pausedTabs) pausedTabs.add(tabId); })();