(async () => { if (window.__scraperPhoneUI) { alert("UI scraper sudah ada di halaman ini."); return; } const CONFIG = { itemSelector: "body", phoneSelector: "", paginationContainerSelector: "body", pageDelay: 2500, maxPages: 100, useNextButton: false, nextButtonSelector: "", nextButtonTextCandidates: ["next", "selanjutnya", ">", "›", "»"], scanWholePageText: true, autoScroll: true, scrollDelay: 800, }; const state = { running: false, stopped: false, currentPage: 1, phones: new Set(), visitedPages: new Set(), logs: [], }; const sleep = (ms) => new Promise((r) => setTimeout(r, ms)); function log(msg) { const time = new Date().toLocaleTimeString(); const text = `[${time}] ${msg}`; state.logs.push(text); console.log(text); const countEl = document.getElementById("__scraper_count"); if (countEl) countEl.textContent = state.phones.size; } function normalizePhone(raw) { if (!raw) return null; let n = String(raw).trim(); n = n.replace(/[^\d+]/g, ""); n = n.replace(/^\++/, "+"); if (n.startsWith("08")) { n = "+62" + n.slice(1); } else if (n.startsWith("8")) { n = "+62" + n; } else if (n.startsWith("628")) { n = "+" + n; } else if (n.startsWith("62")) { n = "+" + n; } else if (n.startsWith("00628")) { n = "+" + n.slice(2); } else if (n.startsWith("0")) { n = "+62" + n.slice(1); } if (!/^\+628\d{6,15}$/.test(n)) return null; return n; } function extractPhonesFromText(text) { if (!text) return []; const matches = text.match(/(?:\+?62|0)8[\d\s\-().]{6,20}/g) || []; const result = []; for (const m of matches) { const normalized = normalizePhone(m); if (normalized) result.push(normalized); } return result; } async function autoScrollPage() { if (!CONFIG.autoScroll) return; let lastHeight = -1; let sameCount = 0; while (sameCount < 2 && !state.stopped) { window.scrollTo(0, document.body.scrollHeight); await sleep(CONFIG.scrollDelay); const h = document.body.scrollHeight; if (h === lastHeight) { sameCount++; } else { sameCount = 0; lastHeight = h; } } window.scrollTo(0, 0); await sleep(300); } function scrapeCurrentPage() { let pagePhones = []; if (CONFIG.phoneSelector) { const phoneEls = [...document.querySelectorAll(CONFIG.phoneSelector)]; for (const el of phoneEls) { const txt = (el.innerText || el.textContent || "").trim(); pagePhones.push(...extractPhonesFromText(txt)); } } if (CONFIG.scanWholePageText) { const root = document.querySelector(CONFIG.itemSelector) || document.body; const wholeText = root.innerText || root.textContent || ""; pagePhones.push(...extractPhonesFromText(wholeText)); } const before = state.phones.size; for (const p of pagePhones) state.phones.add(p); const added = state.phones.size - before; log(`Halaman ${state.currentPage}: ${added} nomor baru, total ${state.phones.size}`); } function getPaginationCandidates() { const container = document.querySelector(CONFIG.paginationContainerSelector) || document.body; return [ ...container.querySelectorAll("a, button, [role='button'], li, span, div") ].filter((el) => { const txt = (el.innerText || el.textContent || "").trim(); if (!txt) return false; if (!/^\d+$/.test(txt)) return false; const style = window.getComputedStyle(el); if (style.display === "none" || style.visibility === "hidden") return false; return true; }); } function findPageButton(pageNum) { const candidates = getPaginationCandidates(); let btn = candidates.find((el) => { const txt = (el.innerText || el.textContent || "").trim(); return txt === String(pageNum); }); if (btn) return btn; btn = [...document.querySelectorAll("a, button, [role='button']")].find((el) => { const aria = (el.getAttribute("aria-label") || "").toLowerCase(); const dataPage = el.getAttribute("data-page"); return ( aria.includes(`page ${pageNum}`) || aria.includes(`halaman ${pageNum}`) || dataPage === String(pageNum) ); }); return btn || null; } function findNextButton() { if (CONFIG.nextButtonSelector) { const el = document.querySelector(CONFIG.nextButtonSelector); if (el) return el; } const all = [...document.querySelectorAll("a, button, [role='button'], li, span")]; return all.find((el) => { const txt = (el.innerText || el.textContent || "").trim().toLowerCase(); const aria = (el.getAttribute("aria-label") || "").trim().toLowerCase(); return CONFIG.nextButtonTextCandidates.some( (k) => txt === k || aria.includes(k) ); }) || null; } async function clickElement(el) { if (!el) return false; el.scrollIntoView({ behavior: "smooth", block: "center" }); await sleep(500); el.click(); return true; } function getPageFingerprint() { const url = location.href; const title = document.title; const paginationTexts = [...document.querySelectorAll("a, button")] .slice(0, 200) .map((el) => (el.innerText || el.textContent || "").trim()) .join("|"); return `${url}__${title}__${paginationTexts.slice(0, 500)}`; } async function waitPageChanged(oldFingerprint, timeout = 10000) { const start = Date.now(); while (Date.now() - start < timeout && !state.stopped) { await sleep(500); const now = getPageFingerprint(); if (now !== oldFingerprint) { await sleep(CONFIG.pageDelay); return true; } } await sleep(CONFIG.pageDelay); return false; } async function goToNextPage() { const oldFingerprint = getPageFingerprint(); if (CONFIG.useNextButton) { const nextBtn = findNextButton(); if (!nextBtn) return false; await clickElement(nextBtn); await waitPageChanged(oldFingerprint); state.currentPage++; return true; } const nextPageNumber = state.currentPage + 1; const btn = findPageButton(nextPageNumber); if (!btn) return false; await clickElement(btn); await waitPageChanged(oldFingerprint); state.currentPage = nextPageNumber; return true; } async function runScraper() { if (state.running) return; state.running = true; state.stopped = false; setRunningUI(true); log("Scraper dimulai..."); try { for (let i = state.currentPage; i <= CONFIG.maxPages; i++) { if (state.stopped) break; await autoScrollPage(); scrapeCurrentPage(); const fp = getPageFingerprint(); if (state.visitedPages.has(fp)) break; state.visitedPages.add(fp); const moved = await goToNextPage(); if (!moved) break; } log(`Selesai. Total unik: ${state.phones.size}`); } catch (err) { console.error(err); log("Error: " + err.message); } finally { state.running = false; setRunningUI(false); } } function copyToClipboard() { const txt = [...state.phones].join("\n"); if (!txt) { alert("Belum ada data nomor."); return; } navigator.clipboard.writeText(txt) .then(() => alert(`Berhasil copy ${state.phones.size} nomor`)) .catch(() => alert("Gagal copy.")); } function stopScraper() { state.stopped = true; setRunningUI(false); log("Stop diminta..."); } function setRunningUI(isRunning) { const scrapBtn = document.getElementById("__scraper_start"); if (!scrapBtn) return; scrapBtn.textContent = isRunning ? "Stop" : "Scrap"; scrapBtn.onclick = isRunning ? stopScraper : runScraper; } function btnStyle(primary = false) { return ` border: 1px solid rgba(255,255,255,0.12); background: ${primary ? "rgba(255,255,255,0.16)" : "rgba(255,255,255,0.08)"}; color: rgba(255,255,255,0.95); border-radius: 999px; padding: 10px 14px; cursor: pointer; font-size: 12px; font-weight: 600; backdrop-filter: blur(8px); -webkit-backdrop-filter: blur(8px); box-shadow: 0 2px 10px rgba(0,0,0,0.12); min-width: 72px; `; } /* ========================= UI MOBILE MINIMAL ========================= */ const wrap = document.createElement("div"); wrap.id = "__scraperPhoneUI"; wrap.style.cssText = ` position: fixed; right: 10px; bottom: 14px; z-index: 2147483647; display: flex; flex-direction: column; align-items: flex-end; gap: 8px; font-family: Arial, sans-serif; `; wrap.innerHTML = `
Total: 0
`; document.body.appendChild(wrap); document.getElementById("__scraper_start").onclick = runScraper; document.getElementById("__scraper_copy").onclick = copyToClipboard; log("UI siap."); })();