{"id":1184,"date":"2025-11-14T06:28:41","date_gmt":"2025-11-14T06:28:41","guid":{"rendered":"https:\/\/abatablaster.xyz\/?page_id=1184"},"modified":"2026-01-09T23:22:28","modified_gmt":"2026-01-09T23:22:28","slug":"whatsapp-group-finder","status":"publish","type":"page","link":"https:\/\/abatablaster.xyz\/index.php\/whatsapp-group-finder\/","title":{"rendered":"Whatsapp &#8211; Group Finder"},"content":{"rendered":"\n<!DOCTYPE html>\n<html lang=\"ms\">\n\n<head>\n    <meta charset=\"UTF-8\" \/>\n    <title>Group Finder \u2013 AbataBlaster<\/title>\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n\n    <script src=\"https:\/\/www.gstatic.com\/firebasejs\/9.22.2\/firebase-app-compat.js\"><\/script>\n    <script src=\"https:\/\/www.gstatic.com\/firebasejs\/9.22.2\/firebase-auth-compat.js\"><\/script>\n    <script src=\"https:\/\/www.gstatic.com\/firebasejs\/9.22.2\/firebase-firestore-compat.js\"><\/script>\n    <script src=\"https:\/\/cdn.jsdelivr.net\/npm\/sweetalert2@11\"><\/script>\n\n    <style>\n        html,\n        body {\n            margin: 0;\n            padding: 0;\n            width: 100%;\n            max-width: 100%;\n            overflow-x: hidden;\n            \/* \ud83d\udd12 elak scroll kiri-kanan untuk keseluruhan page *\/\n        }\n\n        body {\n            background: #e8f5e9;\n            font-family: 'Poppins', Arial, sans-serif;\n        }\n\n        * {\n            box-sizing: border-box;\n            \/* \ud83d\udd27 pastikan padding tak tambah width pelik-pelik *\/\n        }\n\n        @media (max-width: 850px) {\n            .gf-container {\n                max-width: 98vw;\n                padding: 18px 3vw;\n            }\n\n            .gf-table th,\n            .gf-table td {\n                font-size: 13px;\n                padding: 8px 4px;\n            }\n        }\n\n        .gf-logo-wrap {\n            text-align: center;\n            margin: 26px 0 6px;\n        }\n\n        .gf-logo-wrap img {\n            max-width: 180px;\n            border-radius: 12px;\n            box-shadow: 0 4px 10px rgba(0, 0, 0, 0.2);\n        }\n\n        .gf-container {\n            background: #fff;\n            max-width: 1080px;\n            \/* \ud83d\udd3c lebih lebar utk desktop *\/\n            width: 95vw;\n            \/* \ud83d\udd01 guna 95% width viewport *\/\n            margin: 14px auto 30px;\n            padding: 24px 32px 28px 32px;\n            border-radius: 18px;\n            box-shadow: 0 8px 24px rgba(60, 80, 65, 0.08);\n        }\n\n        @media (max-width: 850px) {\n            .gf-container {\n                width: 98vw;\n                \/* \ud83d\udd27 ketatkan sikit utk tablet\/phone *\/\n                padding: 18px 3vw 22px 3vw;\n            }\n        }\n\n        @media (max-width: 500px) {\n            .gf-container {\n                width: 100%;\n                \/* \u2705 ikut lebar skrin tanpa lebihan pixel *\/\n                padding: 16px 4vw 22px 4vw;\n            }\n        }\n\n        .gf-title {\n            font-size: 1.75rem;\n            font-weight: 700;\n            color: #388e3c;\n            margin-bottom: 4px;\n            letter-spacing: 0.5px;\n            text-align: center;\n        }\n\n        .gf-subtitle {\n            text-align: center;\n            font-size: 0.95rem;\n            color: #455a64;\n            margin-bottom: 14px;\n        }\n\n        .gf-wallet {\n            text-align: center;\n            margin-bottom: 20px;\n            font-size: 1.1rem;\n            color: #1b5e20;\n        }\n\n        .gf-wallet span {\n            font-weight: 700;\n            font-size: 1.4rem;\n        }\n\n        .gf-form {\n            background: #f7fbf7;\n            border-radius: 14px;\n            border: 1px solid #d9ead9;\n            padding: 16px 18px 14px 18px;\n            margin-bottom: 20px;\n        }\n\n        .gf-form-row {\n            display: flex;\n            flex-wrap: wrap;\n            gap: 12px;\n            align-items: flex-end;\n            margin-bottom: 10px;\n        }\n\n        .gf-form-group {\n            flex: 1;\n            min-width: 180px;\n        }\n\n        \/* \ud83d\udcf1 Susun menegak di skrin kecil supaya tak terkeluar *\/\n        @media (max-width: 600px) {\n            .gf-form-row {\n                flex-direction: column;\n                align-items: stretch;\n            }\n\n            .gf-form-group {\n                min-width: 100%;\n            }\n\n            .gf-caj {\n                text-align: left;\n                margin-left: 0;\n                margin-top: 8px;\n                margin-bottom: 4px;\n            }\n\n            .gf-btn {\n                width: 100%;\n                text-align: center;\n            }\n        }\n\n        .gf-label {\n            font-size: 0.9rem;\n            font-weight: 600;\n            color: #2e7d32;\n            margin-bottom: 4px;\n            display: block;\n        }\n\n        .gf-input,\n        .gf-select {\n            width: 100%;\n            padding: 9px 10px;\n            border-radius: 8px;\n            border: 1px solid #c8e6c9;\n            font-size: 0.95rem;\n            box-sizing: border-box;\n            outline: none;\n        }\n\n        .gf-input:focus,\n        .gf-select:focus {\n            border-color: #66bb6a;\n            box-shadow: 0 0 0 2px rgba(102, 187, 106, .2);\n        }\n\n        .gf-btn {\n            padding: 10px 20px;\n            background: linear-gradient(90deg, #43a047 60%, #81c784 100%);\n            color: #fff;\n            font-size: 0.98rem;\n            border: none;\n            border-radius: 10px;\n            font-weight: 600;\n            cursor: pointer;\n            white-space: nowrap;\n            transition: background 0.2s, transform 0.1s;\n        }\n\n        .gf-btn:hover {\n            background: linear-gradient(90deg, #388e3c 60%, #66bb6a 100%);\n            transform: translateY(-1px);\n        }\n\n        .gf-btn:disabled {\n            opacity: 0.6;\n            cursor: default;\n            transform: none;\n        }\n\n        .gf-price-note {\n            font-size: 0.85rem;\n            color: #455a64;\n            margin-top: 2px;\n        }\n\n        .gf-price-note b {\n            color: #1b5e20;\n        }\n\n        .gf-caj {\n            font-size: 0.92rem;\n            font-weight: 600;\n            color: #2e7d32;\n            margin-left: auto;\n            text-align: right;\n            min-width: 140px;\n        }\n\n        .gf-note-warning {\n            font-size: 0.83rem;\n            color: #d84315;\n            margin-top: 6px;\n        }\n\n        .gf-results-title {\n            font-size: 1.1rem;\n            font-weight: 600;\n            color: #2e7d32;\n            margin-bottom: 8px;\n            display: flex;\n            justify-content: space-between;\n            align-items: center;\n            gap: 10px;\n        }\n\n        .gf-results-title small {\n            font-size: 0.8rem;\n            color: #607d8b;\n            font-weight: 400;\n        }\n\n        .gf-results-title {\n            font-size: 1.1rem;\n            font-weight: 600;\n            color: #2e7d32;\n            margin-bottom: 8px;\n            display: flex;\n            justify-content: space-between;\n            align-items: center;\n            gap: 10px;\n        }\n\n        .gf-results-title small {\n            font-size: 0.8rem;\n            color: #607d8b;\n            font-weight: 400;\n        }\n\n        .gf-toolbar {\n            display: flex;\n            flex-wrap: wrap;\n            gap: 6px;\n            justify-content: flex-end;\n        }\n\n        @media (max-width: 600px) {\n            .gf-toolbar {\n                justify-content: flex-start;\n            }\n\n            .gf-toolbar .gf-btn {\n                flex: 1 1 100%;\n                margin-right: 0 !important;\n                \/* \u2705 buang margin kanan di mobile *\/\n            }\n        }\n\n        .gf-results-wrap {\n            max-height: 420px;\n            overflow-y: auto;\n            border-radius: 12px;\n            border: 1px solid #e0e0e0;\n            background: #fafafa;\n        }\n\n        .gf-table {\n            width: 100%;\n            border-collapse: collapse;\n            font-size: 0.9rem;\n        }\n\n        .gf-table th,\n        .gf-table td {\n            padding: 8px 10px;\n            text-align: left;\n            border-bottom: 1px solid #e0e0e0;\n            vertical-align: top;\n        }\n\n        .gf-table th {\n            background: #c8e6c9;\n            color: #2e7d32;\n            font-weight: 600;\n            position: sticky;\n            top: 0;\n            z-index: 1;\n        }\n\n        .gf-link {\n            color: #1565c0;\n            text-decoration: none;\n            word-break: break-all;\n        }\n\n        .gf-link:hover {\n            text-decoration: underline;\n        }\n\n        .gf-chip {\n            display: inline-block;\n            padding: 2px 6px;\n            border-radius: 999px;\n            font-size: 0.75rem;\n            background: #e8f5e9;\n            color: #2e7d32;\n            border: 1px solid #c8e6c9;\n        }\n\n        .gf-empty-row {\n            text-align: center;\n            color: #888;\n        }\n\n        .gf-footer-note {\n            margin-top: 10px;\n            font-size: 0.8rem;\n            color: #607d8b;\n            text-align: right;\n        }\n\n        .gf-badge-beta {\n            display: inline-block;\n            padding: 2px 6px;\n            border-radius: 999px;\n            font-size: 0.7rem;\n            background: #fff3e0;\n            color: #ef6c00;\n            border: 1px solid #ffe0b2;\n            margin-left: 4px;\n            vertical-align: middle;\n        }\n    <\/style>\n<\/head>\n\n<body>\n\n    <div class=\"gf-logo-wrap\">\n        <img decoding=\"async\" src=\"https:\/\/abatablaster.xyz\/wp-content\/uploads\/2025\/05\/photo_2024-05-16_15-47-59.jpg\"\n            alt=\"Abata Blaster Logo\">\n    <\/div>\n\n    <div class=\"gf-container\">\n        <div class=\"gf-title\">\n            \ud83d\udd0d Group Finder WhatsApp\n            <span class=\"gf-badge-beta\">BETA<\/span>\n        <\/div>\n        <div class=\"gf-subtitle\">\n            Cari link group WhatsApp yang diindex di Google. Caj berdasarkan bilangan page carian.\n        <\/div>\n\n        <div class=\"gf-wallet\">\n            Baki Wallet:\n            <span id=\"gfWalletBalance\">RM &#8212;<\/span>\n        <\/div>\n\n        <div class=\"gf-form\">\n            <div class=\"gf-form-row\">\n\n                <div class=\"gf-form-group\" style=\"flex: 1.6;\">\n                    <label class=\"gf-label\">Kata kunci carian<\/label>\n                    <input type=\"text\" id=\"gfKeyword\" class=\"gf-input\" placeholder=\"contoh: bisnes shopee\">\n                <\/div>\n\n                <div class=\"gf-form-group\" style=\"width: 130px;\">\n                    <label class=\"gf-label\">Bilangan page<\/label>\n                    <select id=\"gfPages\" class=\"gf-select\">\n                        <option value=\"1\">1 page<\/option>\n                        <option value=\"2\">2 page<\/option>\n                        <option value=\"3\">3 page<\/option>\n                        <option value=\"5\">5 page<\/option>\n                        <option value=\"10\">10 page<\/option>\n                    <\/select>\n                    <div class=\"gf-price-note\">\n                        1 page \u2248 <b>RM0.04<\/b> (4 sen)\n                    <\/div>\n                <\/div>\n\n                <div class=\"gf-form-group\" style=\"flex: 0 0 auto;\">\n                    <div class=\"gf-caj\" id=\"gfCajLabel\">Caj: RM0.04<\/div>\n                    <button class=\"gf-btn\" id=\"gfBtnSearch\">Cari Group<\/button>\n                <\/div>\n            <\/div>\n            <div class=\"gf-note-warning\">\n                \u26a0\ufe0f Caj akan ditolak dari wallet selepas carian berjaya. Jika wallet tak cukup, carian akan dibatalkan.\n            <\/div>\n        <\/div>\n\n        <div class=\"gf-results-title\">\n            <span>Keputusan Carian<\/span>\n            <small id=\"gfResultSummary\">Belum ada carian.<\/small>\n        <\/div>\n\n        <!-- Toolbar aksi selepas carian -->\n        <div class=\"gf-toolbar\" style=\"text-align:right;margin-bottom:6px;font-size:0.8rem;color:#607d8b;\">\n            <button class=\"gf-btn\" id=\"gfBtnClear\" style=\"font-size:0.8rem;padding:6px 10px;margin-right:4px;\n                       background:linear-gradient(90deg,#e53935 60%,#ef5350 100%);\">\n                \ud83e\uddf9 Kosongkan Jadual\n            <\/button>\n            <button class=\"gf-btn\" id=\"gfBtnCopy\" style=\"font-size:0.8rem;padding:6px 10px;margin-right:4px;\">\n                \ud83d\udccb Copy Link\n            <\/button>\n            <button class=\"gf-btn\" id=\"gfBtnCsv\" style=\"font-size:0.8rem;padding:6px 10px;\n                       background:linear-gradient(90deg,#1976d2 60%,#64b5f6 100%);\">\n                \u2b07\ufe0f Download CSV\n            <\/button>\n        <\/div>\n\n        <div class=\"gf-results-wrap\">\n            <table class=\"gf-table\" id=\"gfResultsTable\">\n                <thead>\n                    <tr>\n                        <th style=\"width:50px;\">#<\/th>\n                        <th>Tajuk<\/th>\n                        <th>Link Group<\/th>\n                        <th style=\"width:80px;\">Page<\/th>\n                    <\/tr>\n                <\/thead>\n                <tbody>\n                    <tr>\n                        <td colspan=\"4\" class=\"gf-empty-row\">Tiada data. Sila buat carian.<\/td>\n                    <\/tr>\n                <\/tbody>\n            <\/table>\n        <\/div>\n\n        <div class=\"gf-footer-note\">\n            Backend sokong WhatsApp &#038; Telegram. UI ini buat masa ini fokus pada WhatsApp dahulu.\n        <\/div>\n    <\/div>\n\n    <script>\n        \/\/ --- SETUP FIREBASE ---\n        const firebaseConfig = {\n            apiKey: \"AIzaSyBou8nlJ7uPZ4ioOJapzC8Dn3-K7Qs-yco\",\n            authDomain: \"whatsapp-ai-saas.firebaseapp.com\",\n            projectId: \"whatsapp-ai-saas\",\n            storageBucket: \"whatsapp-ai-saas.firebasestorage.app\",\n            messagingSenderId: \"287462007544\",\n            appId: \"1:287462007544:web:913db884edf906a37cd10d\"\n        };\n        if (!firebase.apps.length) firebase.initializeApp(firebaseConfig);\n        const auth = firebase.auth();\n        const db = firebase.firestore();\n\n        const HARGA_PER_PAGE_SEN = 4;\n        const gfWalletBalanceEl = document.getElementById('gfWalletBalance');\n        const gfKeywordEl = document.getElementById('gfKeyword');\n        const gfPagesEl = document.getElementById('gfPages');\n        const gfCajLabelEl = document.getElementById('gfCajLabel');\n        const gfBtnSearch = document.getElementById('gfBtnSearch');\n        const gfResultsTableBody = document.querySelector('#gfResultsTable tbody');\n        const gfResultSummary = document.getElementById('gfResultSummary');\n        const gfBtnCopy = document.getElementById('gfBtnCopy');\n        const gfBtnCsv = document.getElementById('gfBtnCsv');\n        const gfBtnClear = document.getElementById('gfBtnClear');\n\n        \/\/ simpan semua result terkumpul (untuk table, copy, CSV)\n        let accumulatedResults = [];\n        let lastResultsLinks = [];\n\n        const LS_KEY_RESULTS = \"ABATA_GF_RESULTS_V1\"; \/\/ simpan result terkumpul\n\n        \/\/ mula-mula disable dulu\n        gfBtnCopy.disabled = true;\n        gfBtnCsv.disabled = true;\n        gfBtnClear.disabled = true;\n\n        let currentUid = null;\n\n        auth.onAuthStateChanged(async (user) => {\n            if (!user) {\n                window.location.href = \"https:\/\/abatablaster.xyz\/index.php\/login\/\";\n                return;\n            }\n            currentUid = user.uid;\n            await loadWalletBalance();\n            updateCajLabel();\n\n            \/\/ \ud83d\udd25 LOAD RESULT TERSIMPAN\n            const saved = localStorage.getItem(LS_KEY_RESULTS);\n            if (saved) {\n                try {\n                    accumulatedResults = JSON.parse(saved) || [];\n                    if (accumulatedResults.length > 0) {\n                        renderAccumulatedResults();\n                    }\n                } catch (e) {\n                    console.error(\"Gagal parse localStorage:\", e);\n                }\n            }\n        });\n\n        async function loadWalletBalance() {\n            try {\n                const snap = await db.collection(\"wallet\").doc(currentUid).get({ source: \"server\" });\n                if (snap.exists) {\n                    const data = snap.data() || {};\n                    const balance = Number(data.balance || 0);\n                    gfWalletBalanceEl.textContent = \"RM \" + balance.toFixed(2);\n                } else {\n                    gfWalletBalanceEl.textContent = \"RM 0.00\";\n                }\n            } catch (e) {\n                console.error(\"Gagal load wallet:\", e);\n                gfWalletBalanceEl.textContent = \"RM --\";\n            }\n        }\n\n        function updateCajLabel() {\n            const pages = parseInt(gfPagesEl.value || \"1\", 10);\n            const cajSen = pages * HARGA_PER_PAGE_SEN;\n            const cajRM = (cajSen \/ 100).toFixed(2);\n            gfCajLabelEl.textContent = `Caj: RM${cajRM}`;\n        }\n\n        gfPagesEl.addEventListener('change', updateCajLabel);\n\n        gfBtnSearch.addEventListener('click', async () => {\n            const keyword = gfKeywordEl.value.trim();\n            const pages = parseInt(gfPagesEl.value || \"1\", 10);\n\n            if (!currentUid) {\n                return Swal.fire(\"Ralat\", \"Sila log masuk semula.\", \"error\");\n            }\n\n            if (!keyword) {\n                return Swal.fire(\"Perlu Kata Kunci\", \"Sila masukkan kata kunci carian.\", \"warning\");\n            }\n\n            if (!Number.isFinite(pages) || pages <= 0) {\n                return Swal.fire(\"Ralat\", \"Bilangan page tidak sah.\", \"error\");\n            }\n\n            const cajSen = pages * HARGA_PER_PAGE_SEN;\n            const cajRM = (cajSen \/ 100).toFixed(2);\n\n            const confirm = await Swal.fire({\n                title: \"Sahkan Carian?\",\n                html: `\n                      <div style=\"text-align:left;font-size:0.95rem;\">\n                        <p><b>Kata kunci:<\/b> ${keyword}<\/p>\n                        <p><b>Bilangan page:<\/b> ${pages}<\/p>\n                        <p style=\"margin-top:8px;\"><b>Caj:<\/b> RM${cajRM}<\/p>\n                        <p style=\"font-size:0.85rem;color:#d84315;\">\n                          Caj ini akan ditolak dari wallet jika carian berjaya.\n                        <\/p>\n                      <\/div>\n                    `,\n                icon: \"question\",\n                showCancelButton: true,\n                confirmButtonText: \"Teruskan\",\n                cancelButtonText: \"Batal\",\n                confirmButtonColor: \"#43a047\"\n            });\n\n            if (!confirm.isConfirmed) return;\n\n            await doSearch(keyword, pages);\n        });\n\n        \/\/ === Butang Copy Link ===\n        gfBtnCopy.addEventListener('click', async () => {\n            if (!lastResultsLinks.length) {\n                return Swal.fire(\"Tiada Data\", \"Tiada link untuk disalin.\", \"info\");\n            }\n            const text = lastResultsLinks.join(\"\\n\");\n\n            try {\n                if (navigator.clipboard && navigator.clipboard.writeText) {\n                    await navigator.clipboard.writeText(text);\n                } else {\n                    \/\/ fallback lama\n                    const ta = document.createElement(\"textarea\");\n                    ta.value = text;\n                    ta.style.position = \"fixed\";\n                    ta.style.left = \"-9999px\";\n                    document.body.appendChild(ta);\n                    ta.select();\n                    document.execCommand(\"copy\");\n                    document.body.removeChild(ta);\n                }\n                Swal.fire(\"Berjaya\", \"Semua link telah disalin ke clipboard.\", \"success\");\n            } catch (e) {\n                console.error(\"Copy gagal:\", e);\n                Swal.fire(\"Ralat\", \"Gagal salin ke clipboard.\", \"error\");\n            }\n        });\n\n        \/\/ === Butang Download CSV (1 kolum: link) ===\n        gfBtnCsv.addEventListener('click', () => {\n            if (!lastResultsLinks.length) {\n                return Swal.fire(\"Tiada Data\", \"Tiada link untuk dimuat turun.\", \"info\");\n            }\n            const header = \"link\\n\";\n            const body = lastResultsLinks.join(\"\\n\");\n            const csv = header + body;\n\n            const blob = new Blob([csv], { type: \"text\/csv;charset=utf-8;\" });\n            const url = URL.createObjectURL(blob);\n\n            const a = document.createElement(\"a\");\n            a.href = url;\n            a.download = \"group-links.csv\";\n            document.body.appendChild(a);\n            a.click();\n            document.body.removeChild(a);\n            URL.revokeObjectURL(url);\n        });\n\n        \/\/ === Butang Kosongkan Jadual ===\n        gfBtnClear.addEventListener('click', async () => {\n            if (!accumulatedResults.length) {\n                return Swal.fire(\"Tiada Data\", \"Tiada data untuk dikosongkan.\", \"info\");\n            }\n\n            const confirm = await Swal.fire({\n                title: \"Kosongkan jadual?\",\n                text: \"Semua link carian terkumpul akan dipadam dari jadual.\",\n                icon: \"warning\",\n                showCancelButton: true,\n                confirmButtonText: \"Ya, kosongkan\",\n                cancelButtonText: \"Batal\",\n                confirmButtonColor: \"#e53935\"\n            });\n\n            if (!confirm.isConfirmed) return;\n\n            accumulatedResults = [];\n            lastResultsLinks = [];\n\n            localStorage.removeItem(LS_KEY_RESULTS);\n\n            gfResultsTableBody.innerHTML = `\n                  <tr>\n                    <td colspan=\"4\" class=\"gf-empty-row\">\n                      Jadual telah dikosongkan. Sila buat carian baru.\n                    <\/td>\n                  <\/tr>\n            `;\n            gfResultSummary.textContent = \"Tiada data. Sila buat carian.\";\n            gfBtnCopy.disabled = true;\n            gfBtnCsv.disabled = true;\n            gfBtnClear.disabled = true;\n        });\n\n        async function doSearch(keyword, pages) {\n            try {\n                gfBtnSearch.disabled = true;\n                gfBtnSearch.textContent = \"Mencari...\";\n\n                Swal.fire({\n                    title: \"Sedang mencari group...\",\n                    didOpen: () => Swal.showLoading(),\n                    allowOutsideClick: false,\n                    allowEscapeKey: false\n                });\n\n                const res = await fetch(\"https:\/\/us-central1-whatsapp-ai-saas.cloudfunctions.net\/api\/group-search\/search\", {\n                    method: \"POST\",\n                    headers: { \"Content-Type\": \"application\/json\" },\n                    body: JSON.stringify({\n                        uid: currentUid,\n                        keyword,\n                        pages,\n                        type: \"whatsapp\"\n                    })\n                });\n\n                const data = await res.json().catch(() => null);\n\n                Swal.close();\n\n                if (!res.ok || !data || data.success === false) {\n                    const msg = data && data.error ? data.error : 'Ralat semasa carian group.';\n                    return Swal.fire(\"Ralat\", msg, \"error\");\n                }\n\n                \/\/ --- KEMASKINI WALLET ---\n                if (typeof data.balanceAfter === \"number\") {\n                    gfWalletBalanceEl.textContent = \"RM \" + Number(data.balanceAfter).toFixed(2);\n                } else {\n                    await loadWalletBalance();\n                }\n\n                \/\/ --- PAPAR RESULT DALAM TABLE ---\n                renderResults(data);\n\n                \/\/ ===========================\n                \/\/ \ud83d\udd25 PAPAR POPUP RINGKASAN\n                \/\/ ===========================\n                const total = data.totalResults ?? data.results.length;\n                const normalCajRM = (pages * HARGA_PER_PAGE_SEN) \/ 100;\n                const cajRM = Number(data.cajRM || normalCajRM);\n                const isDisc = data.discounted === true;\n\n                let title = isDisc ? \"Carian Selesai (Diskaun Automatik)\" : \"Carian Selesai\";\n                let html = `\n                  <div style=\"text-align:left;font-size:0.95rem;\">\n                    <p><b>Kata kunci:<\/b> ${keyword}<\/p>\n                    <p><b>Bilangan page:<\/b> ${pages}<\/p>\n                    <p><b>Jumlah hasil dijumpai:<\/b> ${total}<\/p>\n                `;\n\n                if (isDisc) {\n                    html += `\n                        <p style=\"margin-top:10px;\">\n                          <b>Caj asal:<\/b> RM${normalCajRM.toFixed(2)}<br>\n                          <b>Caj selepas diskaun:<\/b> RM${cajRM.toFixed(2)}\n                        <\/p>\n                        <p style=\"font-size:0.85rem;color:#2e7d32;margin-top:6px;\">\n                          Sistem memberi diskaun kerana hasil kurang daripada threshold\n                          <b>${data.threshold}<\/b>.\n                        <\/p>\n                    `;\n                } else {\n                    html += `\n                        <p style=\"margin-top:10px;\">\n                          <b>Caj:<\/b> RM${cajRM.toFixed(2)}\n                        <\/p>\n                      `;\n                }\n\n                html += `<\/div>`;\n\n                Swal.fire({\n                    title,\n                    html,\n                    icon: \"success\",\n                    confirmButtonColor: \"#43a047\"\n                });\n\n            } catch (err) {\n                console.error(\"doSearch error:\", err);\n                Swal.close();\n                Swal.fire(\"Ralat\", \"Gagal hubungi server. Sila cuba lagi.\", \"error\");\n            } finally {\n                gfBtnSearch.disabled = false;\n                gfBtnSearch.textContent = \"Cari Group\";\n            }\n        }\n\n        function renderResults(data) {\n            const results = data.results || [];\n            const keyword = data.keyword || \"\";\n            const pages = data.pages || 1;\n            const cajRM = Number(data.cajRM || (pages * HARGA_PER_PAGE_SEN \/ 100));\n\n            \/\/ \u274c Kalau memang API tak bagi apa-apa result langsung\n            if (!Array.isArray(results) || results.length === 0) {\n                if (accumulatedResults.length === 0) {\n                    gfResultsTableBody.innerHTML = `\n              <tr>\n                <td colspan=\"4\" class=\"gf-empty-row\">\n                  Tiada link group dijumpai untuk \"<b>${escapeHtml(keyword)}<\/b>\".\n                <\/td>\n              <\/tr>\n            `;\n                    gfResultSummary.textContent =\n                        `Tiada link group dijumpai untuk \"${keyword}\".`;\n                    lastResultsLinks = [];\n                    gfBtnCopy.disabled = true;\n                    gfBtnCsv.disabled = true;\n                    gfBtnClear.disabled = true;\n                } else {\n                    gfResultSummary.textContent =\n                        `Tiada result baru untuk \"${keyword}\". Jumlah terkumpul: ${accumulatedResults.length} link.`;\n                }\n                return;\n            }\n\n            \/\/ \ud83d\udd0d Bina set link sedia ada (untuk buang duplicate)\n            const existingLinks = new Set(\n                accumulatedResults\n                    .map(item => (item && item.link) ? String(item.link).trim() : \"\")\n                    .filter(Boolean)\n            );\n\n            \/\/ \ud83d\udd01 Tapis result baru \u2013 hanya ambil link yang belum pernah wujud\n            const freshResults = results.filter(item => {\n                const raw = item && item.link ? String(item.link).trim() : \"\";\n                if (!raw) return false;\n                if (existingLinks.has(raw)) return false;\n                \/\/ elak duplicate dalam batch yang sama\n                existingLinks.add(raw);\n                return true;\n            });\n\n            \/\/ Kalau semua link dalam batch baru tu sebenarnya dah wujud\n            if (freshResults.length === 0) {\n                if (accumulatedResults.length === 0) {\n                    gfResultsTableBody.innerHTML = `\n              <tr>\n                <td colspan=\"4\" class=\"gf-empty-row\">\n                  Tiada link group dijumpai untuk \"<b>${escapeHtml(keyword)}<\/b>\". \n                  (Semua link carian ini mungkin sama \/ tidak sah.)\n                <\/td>\n              <\/tr>\n            `;\n                    gfResultSummary.textContent =\n                        `Tiada link unik dijumpai untuk \"${keyword}\".`;\n                    lastResultsLinks = [];\n                    gfBtnCopy.disabled = true;\n                    gfBtnCsv.disabled = true;\n                    gfBtnClear.disabled = true;\n                } else {\n                    gfResultSummary.textContent =\n                        `Tiada link baru (semua ${results.length} link sudah wujud). Jumlah terkumpul: ${accumulatedResults.length} link.`;\n                    \/\/ kekalkan jadual sedia ada\n                    lastResultsLinks = accumulatedResults\n                        .map(item => (item && item.link) ? String(item.link).trim() : \"\")\n                        .filter(Boolean);\n                    gfBtnCopy.disabled = lastResultsLinks.length === 0;\n                    gfBtnCsv.disabled = lastResultsLinks.length === 0;\n                    gfBtnClear.disabled = accumulatedResults.length === 0;\n                }\n                return;\n            }\n\n            \/\/ \u2705 Ada result unik \u2013 append ke accumulatedResults\n            accumulatedResults = accumulatedResults.concat(freshResults);\n\n            \/\/ \ud83d\udd25 SIMPAN KE LOCAL STORAGE\n            localStorage.setItem(LS_KEY_RESULTS, JSON.stringify(accumulatedResults));\n\n            \/\/ simpan semua link terkumpul untuk copy \/ CSV\n            lastResultsLinks = accumulatedResults\n                .map(item => (item && item.link) ? String(item.link).trim() : \"\")\n                .filter(link => !!link);\n\n            const totalAll = accumulatedResults.length;\n            const totalNew = freshResults.length;\n\n            gfBtnCopy.disabled = lastResultsLinks.length === 0;\n            gfBtnCsv.disabled = lastResultsLinks.length === 0;\n            gfBtnClear.disabled = accumulatedResults.length === 0;\n\n            \/\/ --- Summary text (tunjuk total link terkumpul) ---\n            gfResultSummary.textContent =\n                `Jumlah terkumpul: ${totalAll} link \u2022 Carian terakhir (link baru): ${totalNew} \u2022 ${pages} page \u2022 Caj: RM${cajRM.toFixed(2)}`;\n\n            \/\/ --- Bina jadual dari SEMUA result terkumpul ---\n            let rows = [];\n            accumulatedResults.forEach((item, idx) => {\n                rows.push(`\n          <tr>\n            <td>${idx + 1}<\/td>\n            <td>\n              ${escapeHtml(item.title || \"(Tiada tajuk)\")}\n              <div style=\"margin-top:3px;font-size:0.8rem;color:#607d8b;\">\n                <span class=\"gf-chip\">WhatsApp<\/span>\n              <\/div>\n            <\/td>\n            <td>\n              <a href=\"${escapeAttr(item.link)}\" target=\"_blank\" rel=\"noopener\" class=\"gf-link\">\n                ${escapeHtml(item.link)}\n              <\/a>\n            <\/td>\n            <td>${item.page || \"?\"}<\/td>\n          <\/tr>\n        `);\n            });\n\n            gfResultsTableBody.innerHTML = rows.join('');\n        }\n\n        function renderAccumulatedResults() {\n            if (accumulatedResults.length === 0) {\n                gfResultsTableBody.innerHTML = `\n            <tr>\n                <td colspan=\"4\" class=\"gf-empty-row\">Tiada data. Sila buat carian.<\/td>\n            <\/tr>`;\n                gfResultSummary.textContent = \"Tiada data. Sila buat carian.\";\n                return;\n            }\n\n            lastResultsLinks = accumulatedResults\n                .map(x => x.link)\n                .filter(Boolean);\n\n            gfBtnCopy.disabled = false;\n            gfBtnCsv.disabled = false;\n            gfBtnClear.disabled = false;\n\n            gfResultSummary.textContent =\n                `Jumlah terkumpul: ${accumulatedResults.length} link (dimuat dari cache)`;\n\n            let rows = [];\n            accumulatedResults.forEach((item, idx) => {\n                rows.push(`\n            <tr>\n                <td>${idx + 1}<\/td>\n                <td>${escapeHtml(item.title || \"(Tiada tajuk)\")}<\/td>\n                <td>\n                    <a href=\"${escapeAttr(item.link)}\" target=\"_blank\" class=\"gf-link\">\n                        ${escapeHtml(item.link)}\n                    <\/a>\n                <\/td>\n                <td>${item.page || \"-\"}<\/td>\n            <\/tr>\n        `);\n            });\n\n            gfResultsTableBody.innerHTML = rows.join(\"\");\n        }\n\n        function escapeHtml(str) {\n            return String(str || \"\")\n                .replace(\/&\/g, \"&amp;\")\n                .replace(\/<\/g, \"&lt;\")\n                .replace(\/>\/g, \"&gt;\")\n                .replace(\/\"\/g, \"&quot;\");\n        }\n\n        function escapeAttr(str) {\n            return String(str || \"\").replace(\/\"\/g, \"&quot;\");\n        }\n    <\/script>\n<\/body>\n\n<\/html>\n","protected":false},"excerpt":{"rendered":"<p>Group Finder \u2013 AbataBlaster \ud83d\udd0d Group Finder WhatsApp BETA Cari link group WhatsApp yang diindex di Google. Caj berdasarkan bilangan page carian. Baki Wallet: RM &#8212; Kata kunci carian Bilangan page 1 page2 page3 page5 page10 page 1 page \u2248 RM0.04 (4 sen) Caj: RM0.04 Cari Group \u26a0\ufe0f Caj akan ditolak dari wallet selepas carian [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"parent":0,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"","meta":{"footnotes":""},"class_list":["post-1184","page","type-page","status-publish","hentry"],"_links":{"self":[{"href":"https:\/\/abatablaster.xyz\/index.php\/wp-json\/wp\/v2\/pages\/1184","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/abatablaster.xyz\/index.php\/wp-json\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/abatablaster.xyz\/index.php\/wp-json\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/abatablaster.xyz\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/abatablaster.xyz\/index.php\/wp-json\/wp\/v2\/comments?post=1184"}],"version-history":[{"count":10,"href":"https:\/\/abatablaster.xyz\/index.php\/wp-json\/wp\/v2\/pages\/1184\/revisions"}],"predecessor-version":[{"id":1335,"href":"https:\/\/abatablaster.xyz\/index.php\/wp-json\/wp\/v2\/pages\/1184\/revisions\/1335"}],"wp:attachment":[{"href":"https:\/\/abatablaster.xyz\/index.php\/wp-json\/wp\/v2\/media?parent=1184"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}