/* discgod popup — talks to the background worker only (which owns the token, * the Discogs API calls, and the server I/O). */ const $ = (id) => document.getElementById(id); const send = (msg) => chrome.runtime.sendMessage(msg); function toast(text, kind = "ok") { const t = $("toast"); t.textContent = text; t.className = `toast show ${kind}`; setTimeout(() => (t.className = "toast"), 2600); } function setDot(state) { $("statusDot").className = "dot" + (state ? " " + state : ""); } // --- load config ------------------------------------------------------------ async function loadConfig() { const cfg = await send({ type: "getConfig" }); $("token").value = cfg.token || ""; $("serverUrl").value = cfg.serverUrl || ""; $("intervalHours").value = String(cfg.intervalHours || 12); $("autoCrawl").checked = cfg.autoCrawl !== false; } // --- watchlist -------------------------------------------------------------- function renderSellers(sellers) { const box = $("watchlist"); if (!sellers || !sellers.length) { box.innerHTML = '
No sellers yet — add one above.
'; return; } box.innerHTML = ""; for (const s of sellers) { const el = document.createElement("div"); el.className = "seller"; const last = s.last_crawled_at ? new Date(s.last_crawled_at).toLocaleDateString() : "never"; const statusErr = (s.last_status || "").startsWith("error"); el.innerHTML = `
${escapeHtml(s.username)}
last ${last}${statusErr ? " · failed" : ""}
${s.active_listings ?? 0} `; box.appendChild(el); } box.querySelectorAll("button").forEach((b) => b.addEventListener("click", onSellerAction) ); } async function onSellerAction(e) { const u = e.currentTarget.dataset.u; const act = e.currentTarget.dataset.act; if (act === "remove") { await send({ type: "untrack", username: u }); toast(`Stopped tracking ${u}`); refresh(); } else if (act === "scrape") { await send({ type: "scrapeOne", username: u }); toast(`Scraping ${u}…`); setDot("busy"); pollStatus(); } } async function refresh() { const tracked = await send({ type: "getTracked" }).catch(() => null); if (tracked && tracked.ok) renderSellers(tracked.sellers); else if (tracked && !tracked.ok) toast(tracked.error || "server unreachable", "err"); const stats = await send({ type: "getStats" }).catch(() => null); if (stats && stats.ok) { $("s_active").textContent = fmt(stats.active_listings); $("s_watched").textContent = fmt(stats.sellers_watched); $("s_gone").textContent = fmt(stats.gone_7d); $("s_price").textContent = fmt(stats.price_changes_7d); $("lastCapture").lastElementChild.textContent = stats.last_capture ? new Date(stats.last_capture).toLocaleString() : "–"; setDot("ok"); } else { setDot("err"); } pollStatus(); } async function pollStatus() { const st = await send({ type: "status" }).catch(() => null); const line = $("statusLine"); if (st && st.crawling) { setDot("busy"); line.firstElementChild.textContent = `crawling… ${st.remaining} seller(s) left`; line.lastElementChild.textContent = st.queued ? `${st.queued} queued` : ""; setTimeout(pollStatus, 2000); } else if (st) { line.firstElementChild.textContent = st.lastCrawlAt ? "last sweep " + new Date(st.lastCrawlAt).toLocaleTimeString() : "idle"; line.lastElementChild.textContent = st.queued ? `${st.queued} queued (retrying)` : ""; } } // --- events ----------------------------------------------------------------- $("save").addEventListener("click", async () => { await send({ type: "setConfig", patch: { token: $("token").value.trim(), serverUrl: $("serverUrl").value.trim().replace(/\/$/, ""), intervalHours: parseInt($("intervalHours").value, 10), autoCrawl: $("autoCrawl").checked, }, }); toast("Saved"); refresh(); }); $("testToken").addEventListener("click", async () => { await send({ type: "setConfig", patch: { token: $("token").value.trim() } }); const r = await send({ type: "testToken" }); if (r.ok) toast(`Token OK — signed in as ${r.username}`); else toast(r.error || "token failed", "err"); }); $("track").addEventListener("click", async () => { const username = $("newSeller").value.trim().replace(/^.*\/seller\//, "").split(/[/?]/)[0]; if (!username) return; const filter = parseFilter($("newFilter").value.trim()); const r = await send({ type: "track", body: { username, filter } }); if (r.ok) { $("newSeller").value = ""; $("newFilter").value = ""; toast(`Tracking ${username}`); refresh(); } else toast(r.error || "could not track", "err"); }); $("scrapeAll").addEventListener("click", async () => { await send({ type: "scrapeAll" }); toast("Scraping all watched sellers…"); setDot("busy"); pollStatus(); }); $("refresh").addEventListener("click", refresh); // Enter-to-track $("newSeller").addEventListener("keydown", (e) => { if (e.key === "Enter") $("track").click(); }); // --- helpers ---------------------------------------------------------------- function parseFilter(str) { // "format=Vinyl&ships_from=Australia" -> {format:'Vinyl', ships_from:'Australia'} const out = {}; if (!str) return out; for (const part of str.split("&")) { const [k, v] = part.split("="); if (k && v) out[k.trim()] = v.trim(); } return out; } function fmt(n) { return n == null ? "–" : Number(n).toLocaleString(); } function escapeHtml(s) { return String(s).replace(/[&<>"']/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" }[c])); } function escapeAttr(s) { return escapeHtml(s).replace(/"/g, """); } // --- init ------------------------------------------------------------------- (async function init() { await loadConfig(); await refresh(); })();