/* 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();
})();