• 07-06-2026, 01:51:51
    #1
    https://disk.yandex.com.tr/d/VOAa3TSTr-tHtQ
    güvenliğinden emin değilim denedim bir sorun olmadı çalışıyor.
    Bot benim değildir.
  • 07-06-2026, 02:04:27
    #2
    Arkadaşlar meraklısına kodu inceledim.
    Verilerinizi herhangibi yere çalmıyor tamamen localde instagram apilerine istek atıyor.
    • Profil fotoğrafı yoksa: +4 puan.
    • Kullanıcı adında çok fazla rakam varsa: +3 puan.
    • Tam adı yoksa veya sadece kullanıcı adından oluşuyorsa: +1/2 puan.
    • Onaylı (mavi tikli) hesapsa: -10 puan (bot olamaz).
    olarak değerlendiriyor ve buna göre takipçileri çıkarıyor. Rate limitleri pek incelemedim hesabınızda çok bot varsa geçici/kalıcı engellemelere girebilirsiniz.
    Ek: Ben bu zip'in şuanki haline yorum yaptım yarın değiştirirlerse vb. diye buyrun incelediğim kod burada;
    // ════════════════════════════════════════════════════════════════════════════════
    // bot_delete.js — Instagram Bot Follower Remover (UI Edition v4 — Advanced Engine)
    // ════════════════════════════════════════════════════════════════════════════════
    (function () {
        'use strict';
        if (window.__BD_LOADED__) { alert('Bot Delete already running!'); return; }
        window.__BD_LOADED__ = true;
    
        // ═══ CONFIG ═══
        const CONFIG = {
            // Fetch timing
            FETCH_DELAY_MIN: 800, FETCH_DELAY_MAX: 2200,
            FETCH_PER_PAGE: 12, FETCH_MAX_RETRIES: 5,
            // Remove timing
            REMOVE_DELAY_MIN: 22000, REMOVE_DELAY_MAX: 42000,
            // Micro-batch (smaller batches = more human-like pattern)
            BATCH_SIZE: 6, BATCH_PAUSE_MIN: 50000, BATCH_PAUSE_MAX: 110000,
            // Mega batch
            MEGA_BATCH_SIZE: 30, MEGA_PAUSE_MIN: 300000, MEGA_PAUSE_MAX: 540000,
            // Rate limit backoff
            RATE_LIMIT_INITIAL: 600000, RATE_LIMIT_MAX: 3600000, RATE_LIMIT_MULT: 1.8,
            MAX_ERRORS: 6, ERROR_PAUSE: 900000,
            // Adaptive throttle
            RT_WINDOW: 10, RT_SLOW_THRESHOLD: 1.8, RT_SLOWDOWN_MAX: 3.0,
            // Session health check interval (ms)
            HEALTH_CHECK_INTERVAL: 300000,
            // GraphQL API (faster follower fetching)
            GQL_FOLLOWERS_HASH: '37479f2b8209594dde7facb0d904896a',
            GQL_PER_PAGE: 50,
            GQL_RETRY_AFTER: 600000, // retry GraphQL after 10min if it failed
            // Storage
            STORAGE_PREFIX: 'bd_',
        };
    
        // ═══ i18n ═══
        const LANG = {
            tr: {
                title:'Bot Temizleyici', minimize:'Küçült', close:'Kapat',
                tabDash:'Panel', tabBots:'Bot Listesi', tabLog:'Log', tabWl:'Beyaz Liste', tabSettings:'Ayarlar',
                phaseReady:'Hazır — Başlat\'a tıkla', phaseFetchingFollowing:'Takip edilenler çekiliyor...',
                phaseScanning:'Taranıyor + çıkarılıyor...', phaseRemoving:'Bot çıkarma devam ediyor...',
                phasePaused:'Tarama duraklatıldı — silme devam ediyor', phaseResuming:'Devam ediyor...',
                phaseRateLimit:'Rate limit — bekleniyor...', phaseSessionError:'Oturum hatası — tekrar giriş yap',
                phaseSessionNotFound:'Oturum bulunamadı!', phaseDone:'Tamamlandı! {0} bot çıkarıldı',
                phaseStopped:'Durduruldu — {0} çıkarıldı, {1} kalan', phaseResumable:'Devam edilebilir — {0} sırada',
                phaseMegaPause:'Mega mola — {0}', phaseBatchPause:'Batch mola — {0}',
                phaseAdaptive:'Adaptif yavaşlama aktif — {0}x',
                statFollowing:'Takip Edilen', statFollowers:'Taranan', statBots:'Bot Tespit', statRemoved:'Çıkarılan', statQueue:'Sırada', statFailed:'Başarısız',
                speed:'Hız', btnStart:'BAŞLAT', btnPause:'TARAMAYI DURAKLAT', btnResume:'TARAMAYA DEVAM', btnStop:'DURDUR', btnExport:'RAPOR İNDİR',
                searchPlaceholder:'Kullanıcı adı ara...', botCount:'{0} bot',
                statusPending:'Bekliyor', statusRemoved:'Çıkarıldı', statusFailed:'Hata', statusQueue:'Sırada', statusWhitelist:'Korumalı',
                moreItems:'+{0} daha', noScanYet:'Henüz tarama yapılmadı', noResults:'Sonuç bulunamadı',
                filterAll:'Tümü', filterNoPic:'Resimsiz', filterHigh:'Yüksek Skor', filterSelected:'Seçili',
                selectAll:'Tümünü Seç', deselectAll:'Seçimi Kaldır', removeSelected:'SEÇİLİ {0} BOTU ÇİKAR',
                scoreLabel:'Skor', scoreLow:'Düşük', scoreMed:'Orta', scoreHigh:'Yüksek',
                wlTitle:'Beyaz Liste', wlDesc:'Bu listedeki hesaplar asla çıkarılmaz', wlAdd:'EKLE', wlPlaceholder:'@kullanıcı_adı',
                wlCount:'{0} korumalı hesap', wlRemove:'Çıkar', wlImport:'JSON İÇE AKTAR', wlExport:'DIŞA AKTAR', wlEmpty:'Beyaz liste boş',
                settFetchSpeed:'Fetch Hızı', settMinDelay:'Min gecikme (ms)', settMaxDelay:'Max gecikme (ms)', settPerPage:'Sayfa başı',
                settRemoveSpeed:'Silme Hızı', settBatchSize:'Batch boyutu', settBatchPauseMin:'Batch mola min (ms)', settBatchPauseMax:'Batch mola max (ms)',
                settMegaBatch:'Mega Batch', settMegaSize:'Mega batch boyutu', settMegaPauseMin:'Mega mola min (ms)', settMegaPauseMax:'Mega mola max (ms)',
                btnSaveSettings:'AYARLARI KAYDET', btnResetAll:'TÜM VERİYİ SIFIRLA',
                confirmClose:'İşlem devam ediyor. Kapatılsın mı?', confirmReset:'TÜM veri silinecek. Emin misiniz?',
                logReady:'Bot Temizleyici v4 hazır.', logSessionNotFound:'Oturum bulunamadı',
                logResumeQueue:'Devam: {0} bot sırada', logPhase1:'Phase 1: Following çekiliyor',
                logFollowingCache:'Following cache: {0}', logPhase23:'Phase 2+3: Tarama + silme',
                logScanDone:'Tarama bitti: {0} bot', logDone:'Tamamlandı: {0} çıkarıldı, {1} başarısız',
                logPaused:'Tarama duraklatıldı', logResumed:'Tarama devam', logStopped:'Durduruldu. Oturum: {0}',
                logRateLimit:'Rate limit ({0})', logSessionError:'Oturum hatası',
                logFetchRetry:'{0} hata ({1}), tekrar {2}/{3}', logFetchFail:'{0} fetch başarısız',
                logFetchDone:'{0} tamamlandı: {1}', logFetchProgress:'{0}: {1} çekildi',
                logScanProgress:'Tarama: {0} takipçi, {1} bot', logCsrfError:'CSRF bulunamadı',
                logRateLimitUser:'Rate limit @{0}', logSessionRefresh:'CSRF yenileniyor',
                logRemoved:'@{0} çıkarıldı [{1}] — {2} kalan', logRemoveFailed:'@{0} başarısız ({1})',
                logConsecutiveErrors:'{0} hata — {1} mola', logMegaPause:'Mega mola ({0}) — {1}',
                logBatchPause:'Batch mola ({0}) — {1}', logBackoff:'Backoff ({0}): {1}',
                logSettingsSaved:'Ayarlar kaydedildi', logResetDone:'Sıfırlandı', logExported:'Rapor indirildi',
                logStateLoaded:'Yüklendi: {0} çıkarılmış, {1} kalan', logError:'Hata: {0}',
                logWhitelisted:'@{0} beyaz listeye eklendi', logWlRemoved:'@{0} beyaz listeden çıkarıldı',
                logSkipWhitelist:'@{0} beyaz listede — atlandı', logSelectRemove:'{0} seçili bot çıkarılacak',
                logAdaptiveSlowdown:'Adaptif yavaşlama: {0}x — gecikme artırıldı',
                logSessionHealthOk:'Oturum sağlıklı',
                logSessionHealthFail:'Oturum sağlıksız — bekleniyor',
                logSoftRateLimit:'Soft rate limit algılandı — proaktif yavaşlama',
                logHealthCheck:'Oturum kontrolü yapılıyor...',
                logGqlStart:'GraphQL API ile tarama (50/sayfa)',
                logGqlToRest:'GraphQL limit — REST API\'ye geçildi (12/sayfa)',
                logRestToGql:'GraphQL API\'ye geri dönüldü (50/sayfa)',
                logGqlTotal:'Toplam takipçi: {0}',
                logApiMode:'API: {0} — {1}/sayfa',
                logGqlError:'GraphQL hata: {0}',
                durMin:'dk', durHour:'sa',
            },
            en: {
                title:'Bot Cleaner', minimize:'Minimize', close:'Close',
                tabDash:'Dashboard', tabBots:'Bot List', tabLog:'Log', tabWl:'Whitelist', tabSettings:'Settings',
                phaseReady:'Ready — Click Start', phaseFetchingFollowing:'Fetching following...',
                phaseScanning:'Scanning + removing...', phaseRemoving:'Removing bots...',
                phasePaused:'Scan paused — removal continues', phaseResuming:'Resuming...',
                phaseRateLimit:'Rate limited — waiting...', phaseSessionError:'Session error — log in again',
                phaseSessionNotFound:'Session not found!', phaseDone:'Done! {0} bots removed',
                phaseStopped:'Stopped — {0} removed, {1} remaining', phaseResumable:'Resumable — {0} in queue',
                phaseMegaPause:'Mega break — {0}', phaseBatchPause:'Batch break — {0}',
                phaseAdaptive:'Adaptive throttle active — {0}x',
                statFollowing:'Following', statFollowers:'Scanned', statBots:'Bots', statRemoved:'Removed', statQueue:'Queue', statFailed:'Failed',
                speed:'Speed', btnStart:'START', btnPause:'PAUSE SCAN', btnResume:'RESUME SCAN', btnStop:'STOP ALL', btnExport:'EXPORT REPORT',
                searchPlaceholder:'Search username...', botCount:'{0} bots',
                statusPending:'Pending', statusRemoved:'Removed', statusFailed:'Failed', statusQueue:'Queued', statusWhitelist:'Protected',
                moreItems:'+{0} more', noScanYet:'No scan yet', noResults:'No results',
                filterAll:'All', filterNoPic:'No Pic', filterHigh:'High Score', filterSelected:'Selected',
                selectAll:'Select All', deselectAll:'Deselect All', removeSelected:'REMOVE {0} SELECTED',
                scoreLabel:'Score', scoreLow:'Low', scoreMed:'Med', scoreHigh:'High',
                wlTitle:'Whitelist', wlDesc:'These accounts will never be removed', wlAdd:'ADD', wlPlaceholder:'@username',
                wlCount:'{0} protected', wlRemove:'Remove', wlImport:'IMPORT JSON', wlExport:'EXPORT', wlEmpty:'Whitelist empty',
                settFetchSpeed:'Fetch Speed', settMinDelay:'Min delay (ms)', settMaxDelay:'Max delay (ms)', settPerPage:'Per page',
                settRemoveSpeed:'Remove Speed', settBatchSize:'Batch size', settBatchPauseMin:'Batch pause min (ms)', settBatchPauseMax:'Batch pause max (ms)',
                settMegaBatch:'Mega Batch', settMegaSize:'Mega size', settMegaPauseMin:'Mega min (ms)', settMegaPauseMax:'Mega max (ms)',
                btnSaveSettings:'SAVE SETTINGS', btnResetAll:'RESET ALL DATA',
                confirmClose:'Process running. Close?', confirmReset:'ALL data will be deleted. Sure?',
                logReady:'Bot Cleaner v4 ready.', logSessionNotFound:'Session not found',
                logResumeQueue:'Resuming: {0} in queue', logPhase1:'Phase 1: Fetching following',
                logFollowingCache:'Following cache: {0}', logPhase23:'Phase 2+3: Scan + remove',
                logScanDone:'Scan done: {0} bots', logDone:'Done: {0} removed, {1} failed',
                logPaused:'Scan paused', logResumed:'Scan resumed', logStopped:'Stopped. Session: {0}',
                logRateLimit:'Rate limit ({0})', logSessionError:'Session error',
                logFetchRetry:'{0} error ({1}), retry {2}/{3}', logFetchFail:'{0} fetch failed',
                logFetchDone:'{0} done: {1}', logFetchProgress:'{0}: {1} fetched',
                logScanProgress:'Scan: {0} followers, {1} bots', logCsrfError:'CSRF not found',
                logRateLimitUser:'Rate limit @{0}', logSessionRefresh:'CSRF refreshing',
                logRemoved:'@{0} removed [{1}] — {2} left', logRemoveFailed:'@{0} failed ({1})',
                logConsecutiveErrors:'{0} errors — {1} break', logMegaPause:'Mega break ({0}) — {1}',
                logBatchPause:'Batch break ({0}) — {1}', logBackoff:'Backoff ({0}): {1}',
                logSettingsSaved:'Settings saved', logResetDone:'Reset done', logExported:'Report downloaded',
                logStateLoaded:'Loaded: {0} removed, {1} left', logError:'Error: {0}',
                logWhitelisted:'@{0} whitelisted', logWlRemoved:'@{0} removed from whitelist',
                logSkipWhitelist:'@{0} whitelisted — skipped', logSelectRemove:'{0} selected bots to remove',
                logAdaptiveSlowdown:'Adaptive slowdown: {0}x — delay increased',
                logSessionHealthOk:'Session healthy',
                logSessionHealthFail:'Session unhealthy — waiting',
                logSoftRateLimit:'Soft rate limit detected — proactive slowdown',
                logHealthCheck:'Running session health check...',
                logGqlStart:'Scanning via GraphQL API (50/page)',
                logGqlToRest:'GraphQL limited — switched to REST API (12/page)',
                logRestToGql:'Switched back to GraphQL API (50/page)',
                logGqlTotal:'Total followers: {0}',
                logApiMode:'API: {0} — {1}/page',
                logGqlError:'GraphQL error: {0}',
                durMin:'min', durHour:'h',
            },
        };
        let currentLang = 'tr';
        function t(key, ...args) {
            let s = (LANG[currentLang] && LANG[currentLang][key]) || LANG.tr[key] || key;
            args.forEach((v, i) => { s = s.replace(`{${i}}`, v); });
            return s;
        }
    
        // ═══ STATE ═══
        const state = {
            running: false, scanPaused: false, followingSet: new Set(), followingList: [],
            followersScanned: 0, botsFound: [], removalQueue: [], removedList: [], failedList: [],
            fetchComplete: false, currentBackoff: CONFIG.RATE_LIMIT_INITIAL, consecutiveErrors: 0,
            sessionRemoved: 0, startTime: 0, logs: [],
            whitelist: new Set(), whitelistUsers: [],
            selectedBots: new Set(), selectMode: false, botFilter: 'all',
        };
        const removedIds = new Set(), failedIds = new Set(), queueIds = new Set();
    
        // ═══ UTILS ═══
        const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
        const rand = (a, b) => Math.floor(Math.random() * (b - a + 1)) + a;
        const rdelay = (a, b) => sleep(rand(a, b));
    
        // Box-Muller transform — Gaussian (normal) random distribution
        function gaussRand(mean, stddev) {
            let u1 = Math.random(), u2 = Math.random();
            while (u1 === 0) u1 = Math.random();
            const z = Math.sqrt(-2.0 * Math.log(u1)) * Math.cos(2.0 * Math.PI * u2);
            return Math.max(0, Math.round(mean + z * stddev));
        }
    
        // Human-like delay: Gaussian distribution centered between min and max
        // 95% of values fall within [min, max], with occasional outliers for realism
        function humanDelay(min, max) {
            const mean = (min + max) / 2;
            const stddev = (max - min) / 4;
            const delay = gaussRand(mean, stddev);
            return Math.max(Math.round(min * 0.85), Math.min(Math.round(max * 1.25), delay));
        }
    
        function fmtDur(ms) {
            if (ms < 1000) return ms + 'ms';
            const s = Math.floor(ms / 1000);
            if (s < 60) return s + 's';
            const m = Math.floor(s / 60), rs = s % 60;
            if (m < 60) return m + t('durMin') + ' ' + rs + 's';
            return Math.floor(m / 60) + t('durHour') + ' ' + (m % 60) + t('durMin');
        }
        function fmtTime(d) { return d.toLocaleTimeString(currentLang === 'tr' ? 'tr-TR' : 'en-US', { hour: '2-digit', minute: '2-digit', second: '2-digit' }); }
        function fmtNum(n) { return n.toLocaleString(currentLang === 'tr' ? 'tr-TR' : 'en-US'); }
        function escH(s) { const d = document.createElement('div'); d.textContent = s; return d.innerHTML; }
    
        // ═══ RESPONSE TIME TRACKER (Adaptive Throttle) ═══
        const responseTracker = {
            times: [],
            add(ms) {
                this.times.push(ms);
                if (this.times.length > CONFIG.RT_WINDOW) this.times.shift();
            },
            average() {
                if (this.times.length === 0) return 0;
                return this.times.reduce((a, b) => a + b, 0) / this.times.length;
            },
            isSlowing() {
                if (this.times.length < 3) return false;
                const avg = this.average();
                const recent = this.times.slice(-2);
                const recentAvg = recent.reduce((a, b) => a + b, 0) / recent.length;
                return recentAvg > avg * CONFIG.RT_SLOW_THRESHOLD;
            },
            getMultiplier() {
                if (!this.isSlowing()) return 1.0;
                const avg = this.average();
                const recent = this.times.slice(-2);
                const recentAvg = recent.reduce((a, b) => a + b, 0) / recent.length;
                return Math.min(CONFIG.RT_SLOWDOWN_MAX, recentAvg / avg);
            },
            reset() { this.times = []; },
        };
    
        // ═══ WEB SESSION ID ═══
        function getWebSessionId() {
            let sid = sessionStorage.getItem('bd-web-session-id');
            if (!sid) {
                const chars = 'abcdefghijklmnopqrstuvwxyz0123456789';
                const seg = () => { let s = ''; for (let i = 0; i < 6; i++) s += chars[Math.floor(Math.random() * chars.length)]; return s; };
                sid = `${seg()}:${seg()}:${seg()}`;
                sessionStorage.setItem('bd-web-session-id', sid);
            }
            return sid;
        }
    
        // ═══ BOT SCORE ═══
        function calcScore(u) {
            let s = 0;
            if (u.no_pic) s += 4;
            if (/^\d{5,}/.test(u.username) || (u.username.replace(/[^0-9]/g, '').length / u.username.length) > 0.5) s += 3;
            if (!u.full_name || u.full_name.trim().length === 0) s += 2;
            else if (u.full_name.toLowerCase() === u.username.toLowerCase()) s += 1;
            if (/_{3,}|\.{3,}/.test(u.username)) s += 1;
            if (u.is_private) s += 1;
            if (u.is_verified) s -= 10;
            return Math.max(0, s);
        }
        function scoreClass(s) { return s >= 6 ? 'high' : s >= 3 ? 'med' : 'low'; }
    
        // ═══ STORAGE ═══
        const store = {
            k: (n) => CONFIG.STORAGE_PREFIX + n,
            get(n, fb = null) { try { const r = localStorage.getItem(this.k(n)); return r !== null ? JSON.parse(r) : fb; } catch { return fb; } },
            set(n, v) { try { localStorage.setItem(this.k(n), JSON.stringify(v)); } catch {} },
            del(n) { localStorage.removeItem(this.k(n)); },
            clear() { Object.keys(localStorage).filter(k => k.startsWith(CONFIG.STORAGE_PREFIX)).forEach(k => localStorage.removeItem(k)); },
        };
    
        // ═══ AUTH & API ═══
        function getCookie(n) { const m = document.cookie.split(';').find(c => c.trim().startsWith(n + '=')); return m ? m.split('=')[1].trim() : null; }
        const getCsrf = () => getCookie('csrftoken');
        const getUid = () => getCookie('ds_user_id');
    
        // Full header set matching real Instagram web client
        function getHeaders(csrf) {
            return {
                'accept': '*/*',
                'x-asbd-id': '359341',
                'x-csrftoken': csrf,
                'x-ig-app-id': '936619743392459',
                'x-ig-www-claim': sessionStorage.getItem('www-claim-v2') || '0',
                'x-requested-with': 'XMLHttpRequest',
                'x-web-session-id': getWebSessionId(),
            };
        }
        function postHeaders(csrf) {
            return {
                ...getHeaders(csrf),
                'content-type': 'application/x-www-form-urlencoded',
            };
        }
    
        function saveClaim(r) { const c = r.headers.get('x-ig-set-www-claim'); if (c) sessionStorage.setItem('www-claim-v2', c); }
    
        // API calls with response time tracking
        async function apiGet(url, csrf) {
            const start = performance.now();
            const r = await fetch(url, {
                method: 'GET',
                headers: getHeaders(csrf),
                credentials: 'include',
                referrerPolicy: 'strict-origin-when-cross-origin',
            });
            responseTracker.add(performance.now() - start);
            saveClaim(r);
            return r;
        }
        async function apiPost(url, csrf) {
            const start = performance.now();
            const r = await fetch(url, {
                method: 'POST',
                headers: postHeaders(csrf),
                credentials: 'include',
                body: '',
                referrerPolicy: 'strict-origin-when-cross-origin',
            });
            responseTracker.add(performance.now() - start);
            saveClaim(r);
            return r;
        }
    
        // ═══ SESSION HEALTH CHECK ═══
        async function sessionHealthCheck(csrf) {
            const uid = getUid();
            if (!csrf || !uid) return false;
            try {
                const r = await fetch(`/api/v1/users/${uid}/info/`, {
                    method: 'GET',
                    headers: getHeaders(csrf),
                    credentials: 'include',
                    referrerPolicy: 'strict-origin-when-cross-origin',
                });
                saveClaim(r);
                return r.ok;
            } catch {
                return false;
            }
        }
    
        // ═══ CSS ═══
        function injectCSS() {
            const s = document.createElement('style');
            s.textContent = `
    #bd-panel{position:fixed;top:20px;right:20px;width:440px;max-height:88vh;background:#0d0d1f;border:1px solid #2a2a4a;border-radius:16px;color:#e8e8e8;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;font-size:13px;z-index:999999;box-shadow:0 8px 32px rgba(0,0,0,.6);display:flex;flex-direction:column;overflow:hidden;transition:all .3s}
    #bd-panel.minimized{max-height:48px;width:320px;border-radius:24px}
    #bd-panel.minimized .bd-body{display:none}
    .bd-hdr{display:flex;align-items:center;padding:10px 14px;background:#12122a;cursor:move;user-select:none;border-bottom:1px solid #2a2a4a;min-height:44px;box-sizing:border-box;flex-shrink:0}
    .bd-hdr-icon{font-size:16px;margin-right:6px}.bd-hdr-title{flex:1;font-weight:700;font-size:13px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
    .bd-hdr-badge{background:#e94560;color:#fff;font-size:10px;font-weight:700;padding:2px 7px;border-radius:10px;margin-right:6px}
    .bd-hdr-btn{background:none;border:none;color:#888;font-size:16px;cursor:pointer;padding:2px 6px;transition:color .2s}.bd-hdr-btn:hover{color:#fff}
    .bd-lang-toggle{display:flex;margin-right:6px;border:1px solid #2a2a4a;border-radius:5px;overflow:hidden}
    .bd-lang-btn{background:none;border:none;color:#666;font-size:10px;font-weight:700;cursor:pointer;padding:3px 7px;transition:all .2s}.bd-lang-btn:hover{color:#aaa}.bd-lang-btn.active{background:#e94560;color:#fff}
    .bd-tabs{display:flex;background:#0a0a18;border-bottom:1px solid #2a2a4a;flex-shrink:0}
    .bd-tab{flex:1;padding:8px 2px;background:none;border:none;color:#666;font-size:10px;font-weight:600;cursor:pointer;border-bottom:2px solid transparent;transition:all .2s;text-transform:uppercase;letter-spacing:.3px}.bd-tab:hover{color:#aaa}.bd-tab.active{color:#e94560;border-bottom-color:#e94560}
    .bd-body{flex:1;overflow:hidden;display:flex;flex-direction:column}
    .bd-content{flex:1;overflow-y:auto;padding:14px;display:none}.bd-content.active{display:block}.bd-content::-webkit-scrollbar{width:4px}.bd-content::-webkit-scrollbar-thumb{background:#2a2a4a;border-radius:2px}
    .bd-phase{text-align:center;padding:8px;margin-bottom:10px;background:#1a1a3a;border-radius:8px;font-weight:600;font-size:12px}
    .bd-phase.running{background:#1a2a1a;color:#00e676}.bd-phase.paused{background:#2a2a1a;color:#ffab00}.bd-phase.error{background:#2a1a1a;color:#ff1744}.bd-phase.adaptive{background:#1a1a2a;color:#bb86fc}
    .bd-stats{display:grid;grid-template-columns:1fr 1fr 1fr;gap:6px;margin-bottom:10px}
    .bd-stat{background:#1a1a3a;border-radius:8px;padding:8px;text-align:center}
    .bd-stat-val{font-size:18px;font-weight:700;display:block;margin-bottom:1px}.bd-stat-lbl{font-size:9px;color:#888;text-transform:uppercase;letter-spacing:.4px}
    .bd-stat-val.accent{color:#e94560}.bd-stat-val.success{color:#00e676}.bd-stat-val.info{color:#00b4d8}.bd-stat-val.warn{color:#ffab00}
    .bd-prog{margin-bottom:10px}.bd-prog-bar{height:6px;background:#1a1a3a;border-radius:3px;overflow:hidden;margin-bottom:4px}
    .bd-prog-fill{height:100%;background:linear-gradient(90deg,#e94560,#ff6b81);border-radius:3px;transition:width .5s;width:0%}
    .bd-prog-info{display:flex;justify-content:space-between;font-size:10px;color:#888}
    .bd-ctrls{display:flex;gap:6px;margin-bottom:8px}
    .bd-btn{flex:1;padding:8px;border:none;border-radius:8px;font-weight:700;font-size:11px;cursor:pointer;transition:all .2s;text-transform:uppercase;letter-spacing:.3px}.bd-btn:disabled{opacity:.3;cursor:not-allowed}
    .bd-btn-start{background:#00e676;color:#0a0a1a}.bd-btn-start:hover:not(:disabled){background:#00ff88}
    .bd-btn-pause{background:#ffab00;color:#0a0a1a}.bd-btn-pause:hover:not(:disabled){background:#ffc107}
    .bd-btn-stop{background:#ff1744;color:#fff}.bd-btn-stop:hover:not(:disabled){background:#ff5252}
    .bd-btn-export{background:#1a1a3a;color:#00b4d8;border:1px solid #2a2a4a}.bd-btn-export:hover:not(:disabled){background:#2a2a4a}
    .bd-btn-wl{background:#1a1a3a;color:#bb86fc;border:1px solid #2a2a4a}.bd-btn-wl:hover{background:#2a2a4a}
    .bd-btn-sel{background:#1a2a2a;color:#00e676;border:1px solid #1a3a2a}.bd-btn-sel:hover:not(:disabled){background:#1a3a2a}
    .bd-speed{display:flex;justify-content:space-around;background:#1a1a3a;border-radius:8px;padding:6px;margin-bottom:10px;font-size:11px}.bd-speed span{color:#888}.bd-speed b{color:#e8e8e8}
    .bd-search{width:100%;padding:8px 12px;background:#1a1a3a;border:1px solid #2a2a4a;border-radius:8px;color:#e8e8e8;font-size:12px;margin-bottom:8px;box-sizing:border-box;outline:none}.bd-search:focus{border-color:#e94560}.bd-search::placeholder{color:#555}
    .bd-filters{display:flex;gap:4px;margin-bottom:8px}.bd-fbtn{padding:5px 10px;background:#1a1a3a;border:1px solid #2a2a4a;border-radius:6px;color:#888;font-size:10px;font-weight:600;cursor:pointer;transition:all .2s}.bd-fbtn:hover{color:#ccc}.bd-fbtn.active{background:#e94560;color:#fff;border-color:#e94560}
    .bd-bulk-bar{display:flex;gap:4px;margin-bottom:8px;align-items:center}.bd-bulk-bar .bd-btn{padding:6px 8px;font-size:10px}
    .bd-blist{max-height:calc(85vh - 280px);overflow-y:auto}
    .bd-bitem{display:flex;align-items:center;padding:6px 8px;border-radius:6px;margin-bottom:3px;transition:background .2s}.bd-bitem:hover{background:#1a1a3a}
    .bd-bitem-cb{width:16px;height:16px;margin-right:8px;cursor:pointer;accent-color:#e94560;flex-shrink:0}
    .bd-bitem-avatar{width:32px;height:32px;border-radius:50%;margin-right:8px;background:#2a2a4a;object-fit:cover;flex-shrink:0}
    .bd-bitem-info{flex:1;min-width:0}.bd-bitem-user{font-weight:600;font-size:12px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.bd-bitem-name{font-size:10px;color:#666;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
    .bd-bitem-score{font-size:9px;font-weight:700;padding:2px 6px;border-radius:4px;margin:0 4px;flex-shrink:0}
    .bd-bitem-score.low{background:#1a2a1a;color:#00e676}.bd-bitem-score.med{background:#2a2a1a;color:#ffab00}.bd-bitem-score.high{background:#2a1a1a;color:#ff1744}
    .bd-bitem-status{font-size:9px;font-weight:700;padding:2px 6px;border-radius:4px;flex-shrink:0}
    .bd-bitem-status.pending{background:#2a2a1a;color:#ffab00}.bd-bitem-status.removed{background:#1a2a1a;color:#00e676}.bd-bitem-status.failed{background:#2a1a1a;color:#ff1744}.bd-bitem-status.queue{background:#1a1a2a;color:#00b4d8}.bd-bitem-status.whitelist{background:#1a1a2a;color:#bb86fc}
    .bd-log-box{max-height:calc(85vh - 120px);overflow-y:auto;font-family:'SF Mono',Monaco,Consolas,monospace;font-size:10px;line-height:1.5}
    .bd-log-line{padding:1px 0;border-bottom:1px solid #1a1a2a}.bd-log-time{color:#555;margin-right:4px}
    .bd-log-ok{color:#00e676}.bd-log-warn{color:#ffab00}.bd-log-err{color:#ff1744}.bd-log-info{color:#00b4d8}.bd-log-dim{color:#555}
    .bd-sgroup{margin-bottom:14px}.bd-sgroup-title{font-size:10px;font-weight:700;color:#e94560;text-transform:uppercase;letter-spacing:.4px;margin-bottom:6px}
    .bd-srow{display:flex;align-items:center;justify-content:space-between;margin-bottom:4px;font-size:11px}.bd-srow label{color:#888;flex:1}
    .bd-sinput{width:76px;padding:5px 8px;background:#1a1a3a;border:1px solid #2a2a4a;border-radius:5px;color:#e8e8e8;font-size:11px;text-align:right}.bd-sinput:focus{outline:none;border-color:#e94560}
    .bd-sbtn{width:100%;padding:8px;border:none;border-radius:8px;font-weight:700;font-size:11px;cursor:pointer;margin-top:6px;text-transform:uppercase}
    .bd-sbtn-reset{background:#2a1a1a;color:#ff1744}.bd-sbtn-reset:hover{background:#3a1a1a}
    .bd-sbtn-save{background:#1a2a1a;color:#00e676}.bd-sbtn-save:hover{background:#1a3a1a}
    .bd-empty{text-align:center;padding:30px;color:#555;font-size:12px}
    .bd-queue-label{font-size:10px;color:#888;margin-bottom:6px;display:flex;justify-content:space-between}
    .bd-wl-add{display:flex;gap:6px;margin-bottom:10px}.bd-wl-add input{flex:1}
    .bd-wl-item{display:flex;align-items:center;padding:6px 8px;border-radius:6px;margin-bottom:3px}.bd-wl-item:hover{background:#1a1a3a}
    .bd-wl-item span{flex:1;font-weight:600;font-size:12px}.bd-wl-rm{background:#2a1a1a;border:none;color:#ff1744;padding:4px 10px;border-radius:5px;cursor:pointer;font-size:10px;font-weight:700}.bd-wl-rm:hover{background:#3a1a1a}
    .bd-nopic-icon{color:#ff1744;font-size:10px;margin-right:4px}
    `;
            document.head.appendChild(s);
        }
    
        // ═══ UI BUILD ═══
        let panel, els = {};
        function buildUI() {
            panel = document.createElement('div');
            panel.id = 'bd-panel';
            panel.innerHTML = `
    <div class="bd-hdr" id="bd-drag"><span class="bd-hdr-icon">🤖</span><span class="bd-hdr-title" data-i="title"></span><span class="bd-hdr-badge" id="bd-badge" style="display:none">0</span>
    <div class="bd-lang-toggle"><button class="bd-lang-btn active" data-lang="tr">TR</button><button class="bd-lang-btn" data-lang="en">EN</button></div>
    <button class="bd-hdr-btn" id="bd-min">─</button><button class="bd-hdr-btn" id="bd-close">✕</button></div>
    <div class="bd-body"><div class="bd-tabs">
    <button class="bd-tab active" data-tab="dash" data-i="tabDash"></button>
    <button class="bd-tab" data-tab="bots" data-i="tabBots"></button>
    <button class="bd-tab" data-tab="log" data-i="tabLog"></button>
    <button class="bd-tab" data-tab="wl" data-i="tabWl"></button>
    <button class="bd-tab" data-tab="settings" data-i="tabSettings"></button></div>
    
    <div class="bd-content active" data-tab="dash">
    <div class="bd-phase" id="bd-phase"></div>
    <div class="bd-stats">
    <div class="bd-stat"><span class="bd-stat-val info" id="bd-s-following">—</span><span class="bd-stat-lbl" data-i="statFollowing"></span></div>
    <div class="bd-stat"><span class="bd-stat-val info" id="bd-s-followers">—</span><span class="bd-stat-lbl" data-i="statFollowers"></span></div>
    <div class="bd-stat"><span class="bd-stat-val accent" id="bd-s-bots">0</span><span class="bd-stat-lbl" data-i="statBots"></span></div>
    <div class="bd-stat"><span class="bd-stat-val success" id="bd-s-removed">0</span><span class="bd-stat-lbl" data-i="statRemoved"></span></div>
    <div class="bd-stat"><span class="bd-stat-val warn" id="bd-s-queue">0</span><span class="bd-stat-lbl" data-i="statQueue"></span></div>
    <div class="bd-stat"><span class="bd-stat-val" id="bd-s-failed">0</span><span class="bd-stat-lbl" data-i="statFailed"></span></div></div>
    <div class="bd-prog"><div class="bd-prog-bar"><div class="bd-prog-fill" id="bd-prog-fill"></div></div>
    <div class="bd-prog-info"><span id="bd-prog-pct">%0</span><span id="bd-prog-detail">—</span></div></div>
    <div class="bd-speed"><span><span data-i="speed"></span>: <b id="bd-speed">—</b>/h</span><span>ETA: <b id="bd-eta">—</b></span></div>
    <div class="bd-ctrls"><button class="bd-btn bd-btn-start" id="bd-start"></button><button class="bd-btn bd-btn-pause" id="bd-pause" disabled></button><button class="bd-btn bd-btn-stop" id="bd-stop" disabled></button></div>
    <div class="bd-ctrls"><button class="bd-btn bd-btn-export" id="bd-export" data-i="btnExport"></button></div></div>
    
    <div class="bd-content" data-tab="bots">
    <input class="bd-search" id="bd-search" data-i-placeholder="searchPlaceholder">
    <div class="bd-filters" id="bd-filters">
    <button class="bd-fbtn active" data-filter="all" data-i="filterAll"></button>
    <button class="bd-fbtn" data-filter="nopic" data-i="filterNoPic"></button>
    <button class="bd-fbtn" data-filter="high" data-i="filterHigh"></button>
    <button class="bd-fbtn" data-filter="selected" data-i="filterSelected"></button></div>
    <div class="bd-bulk-bar" id="bd-bulk">
    <button class="bd-btn bd-btn-sel" id="bd-sel-all" data-i="selectAll"></button>
    <button class="bd-btn bd-btn-export" id="bd-desel-all" data-i="deselectAll"></button>
    <button class="bd-btn bd-btn-stop" id="bd-rem-sel" disabled></button></div>
    <div class="bd-queue-label"><span id="bd-blist-count"></span></div>
    <div class="bd-blist" id="bd-blist"></div></div>
    
    <div class="bd-content" data-tab="log"><div class="bd-log-box" id="bd-logbox"></div></div>
    
    <div class="bd-content" data-tab="wl">
    <div class="bd-sgroup"><div class="bd-sgroup-title" data-i="wlTitle"></div><div style="font-size:11px;color:#888;margin-bottom:8px" data-i="wlDesc"></div>
    <div class="bd-wl-add"><input class="bd-search" id="bd-wl-input" data-i-placeholder="wlPlaceholder"><button class="bd-btn bd-btn-wl" id="bd-wl-add-btn" data-i="wlAdd" style="flex:0 0 60px"></button></div></div>
    <div class="bd-queue-label"><span id="bd-wl-count"></span></div>
    <div class="bd-blist" id="bd-wl-list"></div>
    <div class="bd-ctrls" style="margin-top:10px"><button class="bd-btn bd-btn-export" id="bd-wl-import" data-i="wlImport"></button><button class="bd-btn bd-btn-export" id="bd-wl-export" data-i="wlExport"></button></div></div>
    
    <div class="bd-content" data-tab="settings">
    <div class="bd-sgroup"><div class="bd-sgroup-title" data-i="settFetchSpeed"></div>
    <div class="bd-srow"><label data-i="settMinDelay"></label><input class="bd-sinput" data-cfg="FETCH_DELAY_MIN" value="${CONFIG.FETCH_DELAY_MIN}"></div>
    <div class="bd-srow"><label data-i="settMaxDelay"></label><input class="bd-sinput" data-cfg="FETCH_DELAY_MAX" value="${CONFIG.FETCH_DELAY_MAX}"></div>
    <div class="bd-srow"><label data-i="settPerPage"></label><input class="bd-sinput" data-cfg="FETCH_PER_PAGE" value="${CONFIG.FETCH_PER_PAGE}"></div></div>
    <div class="bd-sgroup"><div class="bd-sgroup-title" data-i="settRemoveSpeed"></div>
    <div class="bd-srow"><label data-i="settMinDelay"></label><input class="bd-sinput" data-cfg="REMOVE_DELAY_MIN" value="${CONFIG.REMOVE_DELAY_MIN}"></div>
    <div class="bd-srow"><label data-i="settMaxDelay"></label><input class="bd-sinput" data-cfg="REMOVE_DELAY_MAX" value="${CONFIG.REMOVE_DELAY_MAX}"></div>
    <div class="bd-srow"><label data-i="settBatchSize"></label><input class="bd-sinput" data-cfg="BATCH_SIZE" value="${CONFIG.BATCH_SIZE}"></div>
    <div class="bd-srow"><label data-i="settBatchPauseMin"></label><input class="bd-sinput" data-cfg="BATCH_PAUSE_MIN" value="${CONFIG.BATCH_PAUSE_MIN}"></div>
    <div class="bd-srow"><label data-i="settBatchPauseMax"></label><input class="bd-sinput" data-cfg="BATCH_PAUSE_MAX" value="${CONFIG.BATCH_PAUSE_MAX}"></div></div>
    <div class="bd-sgroup"><div class="bd-sgroup-title" data-i="settMegaBatch"></div>
    <div class="bd-srow"><label data-i="settMegaSize"></label><input class="bd-sinput" data-cfg="MEGA_BATCH_SIZE" value="${CONFIG.MEGA_BATCH_SIZE}"></div>
    <div class="bd-srow"><label data-i="settMegaPauseMin"></label><input class="bd-sinput" data-cfg="MEGA_PAUSE_MIN" value="${CONFIG.MEGA_PAUSE_MIN}"></div>
    <div class="bd-srow"><label data-i="settMegaPauseMax"></label><input class="bd-sinput" data-cfg="MEGA_PAUSE_MAX" value="${CONFIG.MEGA_PAUSE_MAX}"></div></div>
    <button class="bd-sbtn bd-sbtn-save" id="bd-save-cfg" data-i="btnSaveSettings"></button>
    <button class="bd-sbtn bd-sbtn-reset" id="bd-reset-all" data-i="btnResetAll"></button></div>
    </div>`;
            document.body.appendChild(panel);
            cacheEls(); bindEvents(); applyLang(); loadSavedState();
        }
    
        function cacheEls() {
            const q = s => panel.querySelector(s);
            els = {
                phase: q('#bd-phase'), badge: q('#bd-badge'),
                sFollowing: q('#bd-s-following'), sFollowers: q('#bd-s-followers'), sBots: q('#bd-s-bots'),
                sRemoved: q('#bd-s-removed'), sQueue: q('#bd-s-queue'), sFailed: q('#bd-s-failed'),
                progFill: q('#bd-prog-fill'), progPct: q('#bd-prog-pct'), progDetail: q('#bd-prog-detail'),
                speed: q('#bd-speed'), eta: q('#bd-eta'),
                btnStart: q('#bd-start'), btnPause: q('#bd-pause'), btnStop: q('#bd-stop'), btnExport: q('#bd-export'),
                search: q('#bd-search'), blistCount: q('#bd-blist-count'), blist: q('#bd-blist'),
                logbox: q('#bd-logbox'), remSel: q('#bd-rem-sel'),
                wlInput: q('#bd-wl-input'), wlList: q('#bd-wl-list'), wlCount: q('#bd-wl-count'),
            };
        }
    
        // ═══ i18n APPLY ═══
        function applyLang() {
            panel.querySelectorAll('[data-i]').forEach(el => { el.textContent = t(el.dataset.i); });
            panel.querySelectorAll('[data-i-placeholder]').forEach(el => { el.placeholder = t(el.dataset.iPlaceholder); });
            panel.querySelector('#bd-min').title = t('minimize');
            panel.querySelector('#bd-close').title = t('close');
            panel.querySelectorAll('.bd-lang-btn').forEach(b => b.classList.toggle('active', b.dataset.lang === currentLang));
            setButtons(state.running ? (state.scanPaused ? 'scanPaused' : 'running') : 'idle');
            updateSelBtn();
        }
        function switchLang(lang) { if (lang === currentLang) return; currentLang = lang; store.set('lang', lang); applyLang(); renderBotList(); renderWhitelist(); ui_update(); }
    
        // ═══ EVENTS ═══
        function bindEvents() {
            panel.querySelectorAll('.bd-tab').forEach(tab => tab.addEventListener('click', () => {
                panel.querySelectorAll('.bd-tab').forEach(x => x.classList.remove('active'));
                panel.querySelectorAll('.bd-content').forEach(x => x.classList.remove('active'));
                tab.classList.add('active');
                panel.querySelector(`.bd-content[data-tab="${tab.dataset.tab}"]`).classList.add('active');
            }));
            panel.querySelectorAll('.bd-lang-btn').forEach(b => b.addEventListener('click', () => switchLang(b.dataset.lang)));
            // Drag
            let dragging = false, dx = 0, dy = 0;
            panel.querySelector('#bd-drag').addEventListener('mousedown', e => {
                if (e.target.closest('.bd-hdr-btn,.bd-lang-toggle')) return;
                dragging = true; dx = e.clientX - panel.offsetLeft; dy = e.clientY - panel.offsetTop; document.body.style.userSelect = 'none';
            });
            document.addEventListener('mousemove', e => { if (!dragging) return; panel.style.left = Math.max(0, e.clientX - dx) + 'px'; panel.style.top = Math.max(0, e.clientY - dy) + 'px'; panel.style.right = 'auto'; });
            document.addEventListener('mouseup', () => { dragging = false; document.body.style.userSelect = ''; });
            panel.querySelector('#bd-min').addEventListener('click', () => panel.classList.toggle('minimized'));
            panel.querySelector('#bd-close').addEventListener('click', () => { if (state.running && !confirm(t('confirmClose'))) return; state.running = false; panel.remove(); window.__BD_LOADED__ = false; });
            // Controls
            els.btnStart.addEventListener('click', startProcess);
            els.btnPause.addEventListener('click', toggleScanPause);
            els.btnStop.addEventListener('click', stopProcess);
            els.btnExport.addEventListener('click', exportReport);
            els.search.addEventListener('input', renderBotList);
            // Filters
            panel.querySelectorAll('.bd-fbtn').forEach(b => b.addEventListener('click', () => {
                panel.querySelectorAll('.bd-fbtn').forEach(x => x.classList.remove('active'));
                b.classList.add('active'); state.botFilter = b.dataset.filter; renderBotList();
            }));
            // Bulk select
            panel.querySelector('#bd-sel-all').addEventListener('click', () => { getFilteredBots().forEach(b => state.selectedBots.add(b.id)); renderBotList(); updateSelBtn(); });
            panel.querySelector('#bd-desel-all').addEventListener('click', () => { state.selectedBots.clear(); renderBotList(); updateSelBtn(); });
            els.remSel.addEventListener('click', removeSelectedOnly);
            // Whitelist
            panel.querySelector('#bd-wl-add-btn').addEventListener('click', addWhitelist);
            els.wlInput.addEventListener('keydown', e => { if (e.key === 'Enter') addWhitelist(); });
            panel.querySelector('#bd-wl-import').addEventListener('click', importWhitelist);
            panel.querySelector('#bd-wl-export').addEventListener('click', exportWhitelist);
            // Settings
            panel.querySelector('#bd-save-cfg').addEventListener('click', saveSettings);
            panel.querySelector('#bd-reset-all').addEventListener('click', resetAll);
        }
    
        // ═══ UI UPDATE ═══
        function ui_update() {
            els.sFollowing.textContent = fmtNum(state.followingList.length);
            els.sFollowers.textContent = fmtNum(state.followersScanned);
            els.sBots.textContent = fmtNum(state.botsFound.length);
            els.sRemoved.textContent = fmtNum(state.removedList.length);
            els.sQueue.textContent = fmtNum(state.removalQueue.length);
            els.sFailed.textContent = fmtNum(state.failedList.length);
            const total = state.botsFound.length, done = state.removedList.length;
            const pct = total > 0 ? Math.round((done / total) * 100) : 0;
            els.progFill.style.width = pct + '%';
            els.progPct.textContent = '%' + pct;
            els.progDetail.textContent = total > 0 ? `${fmtNum(done)} / ${fmtNum(total)}` : '—';
            els.badge.style.display = state.removalQueue.length > 0 ? '' : 'none';
            els.badge.textContent = state.removalQueue.length;
            if (state.sessionRemoved > 0 && state.startTime > 0) {
                const elapsed = Date.now() - state.startTime, rate = state.sessionRemoved / (elapsed / 3600000);
                els.speed.textContent = Math.round(rate);
                const rem = state.removalQueue.length;
                els.eta.textContent = rem > 0 && rate > 0 ? fmtDur((rem / rate) * 3600000) : '—';
            }
        }
        function setPhase(text, cls = '') { els.phase.textContent = text; els.phase.className = 'bd-phase ' + cls; }
        function addLog(msg, type = 'info') {
            const time = fmtTime(new Date());
            state.logs.push({ time, msg, type }); if (state.logs.length > 500) state.logs.shift();
            const line = document.createElement('div'); line.className = 'bd-log-line';
            line.innerHTML = `<span class="bd-log-time">${time}</span><span class="bd-log-${type}">${escH(msg)}</span>`;
            els.logbox.appendChild(line); els.logbox.scrollTop = els.logbox.scrollHeight;
        }
        function updateSelBtn() {
            const n = state.selectedBots.size;
            els.remSel.disabled = n === 0;
            els.remSel.textContent = t('removeSelected', n);
        }
    
        // ═══ BOT LIST ═══
        function getFilteredBots() {
            const q = (els.search.value || '').toLowerCase().trim();
            let list = state.botsFound;
            if (q) list = list.filter(b => b.username.toLowerCase().includes(q) || (b.full_name || '').toLowerCase().includes(q));
            if (state.botFilter === 'nopic') list = list.filter(b => b.no_pic);
            else if (state.botFilter === 'high') list = list.filter(b => (b.score || 0) >= 6);
            else if (state.botFilter === 'selected') list = list.filter(b => state.selectedBots.has(b.id));
            return list;
        }
        function renderBotList() {
            const list = getFilteredBots();
            els.blistCount.textContent = t('botCount', fmtNum(list.length));
            els.blist.innerHTML = '';
            const frag = document.createDocumentFragment(), max = Math.min(list.length, 200);
            for (let i = 0; i < max; i++) {
                const b = list[i];
                let stCls = 'pending', stTxt = t('statusPending');
                if (state.whitelist.has(b.id)) { stCls = 'whitelist'; stTxt = t('statusWhitelist'); }
                else if (removedIds.has(b.id)) { stCls = 'removed'; stTxt = t('statusRemoved'); }
                else if (failedIds.has(b.id)) { stCls = 'failed'; stTxt = t('statusFailed'); }
                else if (queueIds.has(b.id)) { stCls = 'queue'; stTxt = t('statusQueue'); }
                const sc = b.score || 0, scCls = scoreClass(sc);
                const noPicIcon = b.no_pic ? '<span class="bd-nopic-icon">&#x26D4;</span>' : '';
                const checked = state.selectedBots.has(b.id) ? 'checked' : '';
                const item = document.createElement('div'); item.className = 'bd-bitem';
                item.innerHTML = `<input type="checkbox" class="bd-bitem-cb" data-id="${b.id}" ${checked}>
    ${noPicIcon}<img class="bd-bitem-avatar" src="${b.pic || ''}" onerror="this.style.display='none'" alt="">
    <div class="bd-bitem-info"><div class="bd-bitem-user">@${escH(b.username)}</div><div class="bd-bitem-name">${escH(b.full_name || '')}</div></div>
    <span class="bd-bitem-score ${scCls}">${sc}</span>
    <span class="bd-bitem-status ${stCls}">${stTxt}</span>`;
                frag.appendChild(item);
            }
            if (list.length > 200) { const m = document.createElement('div'); m.className = 'bd-empty'; m.textContent = t('moreItems', fmtNum(list.length - 200)); frag.appendChild(m); }
            if (list.length === 0) { const e = document.createElement('div'); e.className = 'bd-empty'; e.textContent = state.botsFound.length === 0 ? t('noScanYet') : t('noResults'); frag.appendChild(e); }
            els.blist.appendChild(frag);
            // Checkbox events
            els.blist.querySelectorAll('.bd-bitem-cb').forEach(cb => cb.addEventListener('change', () => {
                if (cb.checked) state.selectedBots.add(cb.dataset.id); else state.selectedBots.delete(cb.dataset.id);
                updateSelBtn();
            }));
        }
    
        // ═══ WHITELIST ═══
        function addWhitelist() {
            let name = (els.wlInput.value || '').trim().replace(/^@/, '');
            if (!name) return;
            const bot = state.botsFound.find(b => b.username.toLowerCase() === name.toLowerCase());
            const id = bot ? bot.id : 'wl_' + name.toLowerCase();
            if (state.whitelist.has(id)) return;
            state.whitelist.add(id);
            state.whitelistUsers.push({ id, username: bot ? bot.username : name, full_name: bot ? bot.full_name : '', pic: bot ? bot.pic : '' });
            if (queueIds.has(id)) {
                state.removalQueue = state.removalQueue.filter(b => b.id !== id);
                queueIds.delete(id);
            }
            saveWhitelist(); renderWhitelist(); renderBotList(); ui_update();
            addLog(t('logWhitelisted', name), 'info');
            els.wlInput.value = '';
        }
        function removeWhitelist(id) {
            state.whitelist.delete(id);
            state.whitelistUsers = state.whitelistUsers.filter(u => u.id !== id);
            const username = state.whitelistUsers.find(u => u.id === id)?.username || id;
            saveWhitelist(); renderWhitelist(); renderBotList();
            addLog(t('logWlRemoved', username), 'info');
        }
        function renderWhitelist() {
            els.wlCount.textContent = t('wlCount', state.whitelistUsers.length);
            els.wlList.innerHTML = '';
            if (state.whitelistUsers.length === 0) {
                els.wlList.innerHTML = `<div class="bd-empty">${t('wlEmpty')}</div>`;
                return;
            }
            const frag = document.createDocumentFragment();
            state.whitelistUsers.forEach(u => {
                const item = document.createElement('div'); item.className = 'bd-wl-item';
                item.innerHTML = `<img class="bd-bitem-avatar" src="${u.pic || ''}" onerror="this.style.display='none'" alt="" style="width:28px;height:28px;margin-right:8px">
    <span>@${escH(u.username)}</span><button class="bd-wl-rm" data-id="${u.id}">${t('wlRemove')}</button>`;
                frag.appendChild(item);
            });
            els.wlList.appendChild(frag);
            els.wlList.querySelectorAll('.bd-wl-rm').forEach(b => b.addEventListener('click', () => removeWhitelist(b.dataset.id)));
        }
        function saveWhitelist() { store.set('whitelist', state.whitelistUsers); }
        function importWhitelist() {
            const input = document.createElement('input'); input.type = 'file'; input.accept = '.json';
            input.onchange = () => {
                const reader = new FileReader();
                reader.onload = () => {
                    try {
                        const data = JSON.parse(reader.result);
                        const users = Array.isArray(data) ? data : (data.users || []);
                        users.forEach(u => {
                            const id = u.id || 'wl_' + (u.username || '').toLowerCase();
                            if (!state.whitelist.has(id)) {
                                state.whitelist.add(id);
                                state.whitelistUsers.push({ id, username: u.username || '', full_name: u.full_name || '', pic: u.pic || '' });
                            }
                        });
                        saveWhitelist(); renderWhitelist(); renderBotList();
                        addLog(`Whitelist imported: ${users.length}`, 'ok');
                    } catch { addLog('JSON parse error', 'err'); }
                };
                reader.readAsText(input.files[0]);
            };
            input.click();
        }
        function exportWhitelist() {
            const blob = new Blob([JSON.stringify(state.whitelistUsers, null, 2)], { type: 'application/json' });
            const a = document.createElement('a'); a.href = URL.createObjectURL(blob);
            a.download = 'whitelist.json'; document.body.appendChild(a); a.click(); a.remove(); URL.revokeObjectURL(a.href);
        }
    
        // ═══ SAVED STATE ═══
        function loadSavedState() {
            currentLang = store.get('lang', 'tr'); applyLang();
            const wl = store.get('whitelist', []);
            wl.forEach(u => { state.whitelist.add(u.id); state.whitelistUsers.push(u); });
            renderWhitelist();
            const saved = store.get('state');
            if (!saved) return;
            state.followingList = saved.followingList || [];
            state.followingSet = new Set(state.followingList.map(u => u.id));
            state.botsFound = saved.botsFound || [];
            state.removedList = saved.removedList || [];
            state.failedList = saved.failedList || [];
            state.followersScanned = saved.followersScanned || 0;
            state.fetchComplete = saved.fetchComplete || false;
            state.removedList.forEach(u => removedIds.add(u.id));
            state.failedList.forEach(u => failedIds.add(u.id));
            if (state.botsFound.length > 0) {
                const done = new Set([...removedIds, ...failedIds, ...state.whitelist]);
                state.removalQueue = state.botsFound.filter(b => !done.has(b.id));
                state.removalQueue.forEach(u => queueIds.add(u.id));
            }
            state.botsFound.forEach(b => { b.score = calcScore(b); });
            state.botsFound.sort((a, b) => (b.score || 0) - (a.score || 0));
            ui_update(); renderBotList();
            if (state.removedList.length > 0 || state.botsFound.length > 0) {
                addLog(t('logStateLoaded', state.removedList.length, state.removalQueue.length), 'info');
                setPhase(t('phaseResumable', state.removalQueue.length));
            }
            const cfg = store.get('config');
            if (cfg) { Object.assign(CONFIG, cfg); panel.querySelectorAll('.bd-sinput[data-cfg]').forEach(i => { if (CONFIG[i.dataset.cfg] !== undefined) i.value = CONFIG[i.dataset.cfg]; }); }
        }
        function saveState() {
            store.set('state', {
                followingList: state.followingList, botsFound: state.botsFound,
                removedList: state.removedList, failedList: state.failedList,
                followersScanned: state.followersScanned, fetchComplete: state.fetchComplete,
            });
        }
    
        // ═══ SETTINGS ═══
        function saveSettings() {
            panel.querySelectorAll('.bd-sinput[data-cfg]').forEach(i => { const v = parseInt(i.value, 10); if (!isNaN(v) && v > 0) CONFIG[i.dataset.cfg] = v; });
            store.set('config', CONFIG); addLog(t('logSettingsSaved'), 'ok');
        }
        function resetAll() {
            if (!confirm(t('confirmReset'))) return;
            state.running = false; state.scanPaused = false; store.clear();
            state.followingSet.clear(); state.followingList = []; state.followersScanned = 0;
            state.botsFound = []; state.removalQueue = []; state.removedList = []; state.failedList = [];
            state.fetchComplete = false; state.sessionRemoved = 0;
            state.whitelist.clear(); state.whitelistUsers = []; state.selectedBots.clear();
            removedIds.clear(); failedIds.clear(); queueIds.clear();
            responseTracker.reset();
            els.logbox.innerHTML = ''; state.logs = [];
            setPhase(t('phaseReady')); ui_update(); renderBotList(); renderWhitelist(); setButtons('idle');
            addLog(t('logResetDone'), 'warn');
        }
        function exportReport() {
            const data = { version: 'v4', engine: 'advanced', following: state.followingList.length, scanned: state.followersScanned, bots: state.botsFound.length, removed: state.removedList, failed: state.failedList, remaining: state.removalQueue.length, at: new Date().toISOString() };
            const blob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' });
            const a = document.createElement('a'); a.href = URL.createObjectURL(blob);
            a.download = `bot_report_${new Date().toISOString().slice(0, 10)}.json`; document.body.appendChild(a); a.click(); a.remove(); URL.revokeObjectURL(a.href);
            addLog(t('logExported'), 'ok');
        }
    
        // ═══ BUTTONS ═══
        function setButtons(mode) {
            els.btnStart.disabled = mode !== 'idle';
            els.btnPause.disabled = mode === 'idle' || mode === 'done';
            els.btnStop.disabled = mode === 'idle' || mode === 'done';
            els.btnPause.textContent = state.scanPaused ? t('btnResume') : t('btnPause');
            if (mode === 'idle' && state.removalQueue.length > 0) { els.btnStart.textContent = t('btnResume').replace('SCAN', '').replace('TARAMA', '').trim() || t('btnStart'); els.btnStart.disabled = false; }
            else els.btnStart.textContent = t('btnStart');
            els.btnStop.textContent = t('btnStop');
        }
    
        // ═══ CONTROLS ═══
        async function startProcess() {
            const csrf = getCsrf(), uid = getUid();
            if (!csrf || !uid) { setPhase(t('phaseSessionNotFound'), 'error'); addLog(t('logSessionNotFound'), 'err'); return; }
            state.running = true; state.scanPaused = false; state.startTime = Date.now(); state.sessionRemoved = 0;
            responseTracker.reset();
            setButtons('running');
    
            if (state.removalQueue.length > 0 && state.fetchComplete) {
                addLog(t('logResumeQueue', state.removalQueue.length), 'info');
                setPhase(t('phaseRemoving'), 'running');
                await runRemover(csrf); return;
            }
    
            if (state.followingList.length === 0) {
                setPhase(t('phaseFetchingFollowing'), 'running');
                addLog(t('logPhase1'), 'info');
                if (!await fetchFollowing(uid, csrf)) return;
            } else {
                state.followingSet = new Set(state.followingList.map(u => u.id));
                addLog(t('logFollowingCache', state.followingList.length), 'info');
            }
    
            setPhase(t('phaseScanning'), 'running');
            addLog(t('logPhase23'), 'info');
            const fetchDone = fetchFollowersAndQueue(uid, csrf);
            const removeDone = runRemover(csrf);
            await fetchDone;
            state.fetchComplete = true; saveState();
            addLog(t('logScanDone', state.botsFound.length), 'info');
            await removeDone;
    
            if (state.running) {
                setPhase(t('phaseDone', state.removedList.length), 'running');
                addLog(t('logDone', state.removedList.length, state.failedList.length), 'ok');
                state.running = false; setButtons('done');
            }
        }
    
        function toggleScanPause() {
            state.scanPaused = !state.scanPaused;
            if (state.scanPaused) {
                setPhase(t('phasePaused'), 'paused');
                addLog(t('logPaused'), 'warn');
            } else {
                setPhase(t('phaseResuming'), 'running');
                addLog(t('logResumed'), 'info');
            }
            setButtons(state.scanPaused ? 'scanPaused' : 'running');
        }
    
        function stopProcess() {
            state.running = false; state.scanPaused = false;
            setPhase(t('phaseStopped', state.removedList.length, state.removalQueue.length));
            addLog(t('logStopped', state.sessionRemoved), 'warn');
            saveState(); setButtons('idle');
        }
    
        async function waitScanPause() { while (state.scanPaused && state.running) await sleep(500); }
    
        // ═══ REMOVE SELECTED ONLY ═══
        async function removeSelectedOnly() {
            if (state.selectedBots.size === 0) return;
            const csrf = getCsrf();
            if (!csrf) { addLog(t('logCsrfError'), 'err'); return; }
            const selList = state.botsFound.filter(b => state.selectedBots.has(b.id) && !removedIds.has(b.id) && !failedIds.has(b.id) && !state.whitelist.has(b.id));
            addLog(t('logSelectRemove', selList.length), 'info');
            state.removalQueue = selList;
            queueIds.clear(); selList.forEach(b => queueIds.add(b.id));
            state.fetchComplete = true;
            state.running = true; state.scanPaused = false; state.startTime = Date.now(); state.sessionRemoved = 0;
            responseTracker.reset();
            setButtons('running');
            setPhase(t('phaseRemoving'), 'running');
            ui_update(); renderBotList();
            await runRemover(csrf);
            if (state.running) {
                state.running = false; setButtons('done');
                setPhase(t('phaseDone', state.removedList.length), 'running');
            }
            state.selectedBots.clear(); updateSelBtn(); renderBotList();
        }
    
        // ═══ PHASE 1: FETCH FOLLOWING ═══
        async function fetchFollowing(uid, csrf) {
            let cursor = null, hasMore = true, retries = 0;
            while (hasMore && state.running) {
                await waitScanPause(); if (!state.running) return false;
                let url = `/api/v1/friendships/${uid}/following/?count=${CONFIG.FETCH_PER_PAGE}&search_surface=follow_list_page`;
                if (cursor) url += `&max_id=${cursor}`;
                try {
                    const r = await apiGet(url, csrf);
                    if (r.status === 429) { addLog(t('logRateLimit', 'following'), 'warn'); await handleBackoff(); continue; }
                    if (r.status === 401 || r.status === 403) { setPhase(t('phaseSessionError'), 'error'); addLog(t('logSessionError'), 'err'); state.running = false; setButtons('idle'); return false; }
                    if (!r.ok) { retries++; if (retries > CONFIG.FETCH_MAX_RETRIES) { addLog(t('logFetchFail', 'Following'), 'err'); state.running = false; setButtons('idle'); return false; } addLog(t('logFetchRetry', 'Following', r.status, retries, CONFIG.FETCH_MAX_RETRIES), 'warn'); await sleep(30000); continue; }
                    const data = await r.json(); retries = 0; resetBackoff();
                    if (data.users) for (const u of data.users) { const id = String(u.pk || u.pk_id); if (!state.followingSet.has(id)) { state.followingSet.add(id); state.followingList.push({ id, username: u.username }); } }
                    cursor = data.next_max_id || null; hasMore = !!cursor;
                    els.sFollowing.textContent = fmtNum(state.followingList.length);
                    addLog(t('logFetchProgress', 'Following', state.followingList.length), 'dim');
                    if (hasMore) {
                        // Gaussian delay with adaptive multiplier
                        const mult = responseTracker.getMultiplier();
                        const delay = Math.round(humanDelay(CONFIG.FETCH_DELAY_MIN, CONFIG.FETCH_DELAY_MAX) * mult);
                        if (mult > 1.0) addLog(t('logAdaptiveSlowdown', mult.toFixed(1)), 'warn');
                        await sleep(delay);
                    }
                } catch (e) { addLog(t('logError', e.message), 'err'); retries++; if (retries > CONFIG.FETCH_MAX_RETRIES) { state.running = false; return false; } await sleep(30000); }
            }
            addLog(t('logFetchDone', 'Following', state.followingList.length), 'ok'); saveState(); return true;
        }
    
        // ═══ PHASE 2: FETCH FOLLOWERS + QUEUE (Dual API: GraphQL primary → REST fallback) ═══
    
        // Normalize user data from either API into internal format
        function normalizeUser(u, source) {
            if (source === 'gql') {
                return {
                    id: String(u.id), username: u.username,
                    full_name: u.full_name || '', pic: u.profile_pic_url || '',
                    is_private: u.is_private || false, is_verified: u.is_verified || false,
                    no_pic: u.has_anonymous_profile_picture || false,
                };
            }
            return {
                id: String(u.pk || u.pk_id), username: u.username,
                full_name: u.full_name || '', pic: u.profile_pic_url || '',
                is_private: u.is_private || false, is_verified: u.is_verified || false,
                no_pic: u.has_anonymous_profile_picture || false,
            };
        }
    
        // Process fetched users into bot queue (shared by both APIs)
        function processFollowerBatch(users) {
            let newBots = 0;
            for (const u of users) {
                state.followersScanned++;
                if (!state.followingSet.has(u.id) && !state.whitelist.has(u.id)) {
                    if (!removedIds.has(u.id) && !failedIds.has(u.id) && !queueIds.has(u.id)) {
                        const bot = { ...u };
                        bot.score = calcScore(bot);
                        state.botsFound.push(bot);
                        state.removalQueue.push(bot); queueIds.add(u.id);
                        newBots++;
                    }
                }
            }
            if (newBots > 0) state.removalQueue.sort((a, b) => (b.score || 0) - (a.score || 0));
            return newBots;
        }
    
        async function fetchFollowersAndQueue(uid, csrf) {
            let useGQL = true;
            let gqlCursor = store.get('gql_cursor', null);
            let restCursor = store.get('followers_cursor', null);
            let gqlDisabledAt = 0;
            let hasMore = true, retries = 0;
            let totalReported = false;
    
            addLog(t('logGqlStart'), 'info');
    
            while (hasMore && state.running) {
                await waitScanPause(); if (!state.running) return;
    
                // Try switching back to GraphQL after cooldown
                if (!useGQL && gqlDisabledAt > 0 && Date.now() - gqlDisabledAt > CONFIG.GQL_RETRY_AFTER) {
                    useGQL = true; gqlDisabledAt = 0;
                    addLog(t('logRestToGql'), 'info');
                }
    
                let fetchedUsers = [];
    
                if (useGQL) {
                    // ── GraphQL API (50/page — primary) ──
                    const vars = { id: uid, first: CONFIG.GQL_PER_PAGE };
                    if (gqlCursor) vars.after = gqlCursor;
                    const url = `/graphql/query/?query_hash=${CONFIG.GQL_FOLLOWERS_HASH}&variables=${encodeURIComponent(JSON.stringify(vars))}`;
    
                    try {
                        const r = await apiGet(url, csrf);
    
                        if (r.status === 429) {
                            // GraphQL rate limited — switch to REST (different rate limit bucket)
                            addLog(t('logGqlToRest'), 'warn');
                            useGQL = false; gqlDisabledAt = Date.now();
                            retries = 0;
                            continue;
                        }
                        if (r.status === 401 || r.status === 403) {
                            addLog(t('logSessionError'), 'err');
                            await sleep(5000); csrf = getCsrf();
                            if (!csrf) { state.running = false; return; }
                            continue;
                        }
                        if (!r.ok) {
                            retries++;
                            if (retries > 2) {
                                addLog(t('logGqlToRest'), 'warn');
                                useGQL = false; gqlDisabledAt = Date.now(); retries = 0;
                                continue;
                            }
                            addLog(t('logGqlError', r.status), 'warn');
                            await sleep(10000);
                            continue;
                        }
    
                        const data = await r.json();
                        const edge = data?.data?.user?.edge_followed_by;
    
                        if (!edge) {
                            addLog(t('logGqlToRest'), 'warn');
                            useGQL = false; gqlDisabledAt = Date.now();
                            continue;
                        }
    
                        retries = 0; resetBackoff();
    
                        // Report total follower count (once)
                        if (!totalReported && edge.count) {
                            addLog(t('logGqlTotal', fmtNum(edge.count)), 'info');
                            totalReported = true;
                        }
    
                        fetchedUsers = (edge.edges || []).map(e => normalizeUser(e.node, 'gql'));
                        hasMore = edge.page_info?.has_next_page || false;
                        gqlCursor = edge.page_info?.end_cursor || null;
                        store.set('gql_cursor', gqlCursor);
    
                    } catch (e) {
                        addLog(t('logGqlError', e.message), 'warn');
                        useGQL = false; gqlDisabledAt = Date.now();
                        continue;
                    }
                } else {
                    // ── REST API fallback (12/page) ──
                    let url = `/api/v1/friendships/${uid}/followers/?count=${CONFIG.FETCH_PER_PAGE}&search_surface=follow_list_page`;
                    if (restCursor) url += `&max_id=${restCursor}`;
    
                    try {
                        const r = await apiGet(url, csrf);
    
                        if (r.status === 429) {
                            addLog(t('logRateLimit', 'REST'), 'warn');
                            await handleBackoff();
                            continue;
                        }
                        if (r.status === 401 || r.status === 403) {
                            addLog(t('logSessionError'), 'err');
                            await sleep(5000); csrf = getCsrf();
                            if (!csrf) { state.running = false; return; }
                            continue;
                        }
                        if (!r.ok) {
                            retries++;
                            if (retries > CONFIG.FETCH_MAX_RETRIES) {
                                addLog(t('logFetchFail', 'Followers'), 'err');
                                return;
                            }
                            await sleep(30000);
                            continue;
                        }
    
                        const data = await r.json();
                        retries = 0; resetBackoff();
    
                        fetchedUsers = (data.users || []).map(u => normalizeUser(u, 'rest'));
                        hasMore = !!(data.next_max_id);
                        restCursor = data.next_max_id || null;
                        store.set('followers_cursor', restCursor);
    
                    } catch (e) {
                        addLog(t('logError', e.message), 'err');
                        retries++;
                        if (retries > CONFIG.FETCH_MAX_RETRIES) return;
                        await sleep(30000);
                        continue;
                    }
                }
    
                // ── Process fetched users (shared logic) ──
                if (fetchedUsers.length > 0) {
                    processFollowerBatch(fetchedUsers);
                    ui_update();
                    if (state.followersScanned % 500 === 0) { saveState(); renderBotList(); }
                    const apiLabel = useGQL ? 'GQL' : 'REST';
                    addLog(t('logScanProgress', fmtNum(state.followersScanned), state.botsFound.length) + ` [${apiLabel}]`, 'dim');
                }
    
                if (hasMore) {
                    const mult = responseTracker.getMultiplier();
                    const delay = Math.round(humanDelay(CONFIG.FETCH_DELAY_MIN, CONFIG.FETCH_DELAY_MAX) * mult);
                    if (mult > 1.0) addLog(t('logAdaptiveSlowdown', mult.toFixed(1)), 'warn');
                    await sleep(delay);
                }
            }
    
            state.botsFound.sort((a, b) => (b.score || 0) - (a.score || 0));
            renderBotList();
        }
    
        // ═══ PHASE 3: REMOVER (Advanced Engine — micro-batch + adaptive + health check) ═══
        async function runRemover(csrf) {
            let batch = 0, mega = 0;
            let lastHealthCheck = Date.now();
            let adaptiveNotified = false;
    
            while (state.running) {
                if (!state.running) break;
                if (state.removalQueue.length === 0) { if (state.fetchComplete) break; await sleep(2000); continue; }
    
                // Session health check every HEALTH_CHECK_INTERVAL
                if (Date.now() - lastHealthCheck > CONFIG.HEALTH_CHECK_INTERVAL) {
                    addLog(t('logHealthCheck'), 'dim');
                    const fresh = getCsrf();
                    if (fresh && await sessionHealthCheck(fresh)) {
                        addLog(t('logSessionHealthOk'), 'ok');
                    } else {
                        addLog(t('logSessionHealthFail'), 'warn');
                        setPhase(t('phaseRateLimit'), 'paused');
                        await sleep(60000);
                        lastHealthCheck = Date.now();
                        continue;
                    }
                    lastHealthCheck = Date.now();
                }
    
                const bot = state.removalQueue[0];
    
                // Skip whitelisted
                if (state.whitelist.has(bot.id)) { state.removalQueue.shift(); queueIds.delete(bot.id); addLog(t('logSkipWhitelist', bot.username), 'dim'); continue; }
    
                const fresh = getCsrf();
                if (!fresh) { addLog(t('logCsrfError'), 'err'); setPhase(t('phaseSessionError'), 'error'); state.running = false; setButtons('idle'); break; }
                csrf = fresh;
    
                try {
                    const r = await apiPost(`/api/v1/friendships/remove_follower/${bot.id}/`, csrf);
    
                    // Check adaptive throttle AFTER the request
                    const rtMult = responseTracker.getMultiplier();
                    if (rtMult > 1.0 && !adaptiveNotified) {
                        addLog(t('logSoftRateLimit'), 'warn');
                        adaptiveNotified = true;
                    } else if (rtMult <= 1.0) {
                        adaptiveNotified = false;
                    }
    
                    if (r.status === 429) {
                        addLog(t('logRateLimitUser', bot.username), 'warn');
                        setPhase(t('phaseRateLimit'), 'paused');
                        await handleBackoff();
                        if (state.running) setPhase(state.scanPaused ? t('phasePaused') : t('phaseResuming'), state.scanPaused ? 'paused' : 'running');
                        continue;
                    }
                    if (r.status === 401 || r.status === 403) { addLog(t('logSessionRefresh'), 'warn'); await sleep(5000); continue; }
    
                    if (r.ok || r.status === 400) {
                        state.removalQueue.shift(); queueIds.delete(bot.id); removedIds.add(bot.id);
                        state.removedList.push({ id: bot.id, username: bot.username, at: new Date().toISOString() });
                        state.sessionRemoved++; batch++; mega++; resetBackoff();
                        addLog(t('logRemoved', bot.username, state.removedList.length, state.removalQueue.length), 'ok');
                        ui_update();
                        if (state.sessionRemoved % 10 === 0) { saveState(); renderBotList(); }
    
                        // Micro-batch pause logic
                        if (mega >= CONFIG.MEGA_BATCH_SIZE) {
                            const p = humanDelay(CONFIG.MEGA_PAUSE_MIN, CONFIG.MEGA_PAUSE_MAX);
                            addLog(t('logMegaPause', mega, fmtDur(p)), 'warn');
                            setPhase(t('phaseMegaPause', fmtDur(p)), 'paused'); await sleep(p); mega = 0; batch = 0;
                            responseTracker.reset(); adaptiveNotified = false;
                            if (state.running) setPhase(state.scanPaused ? t('phasePaused') : t('phaseResuming'), state.scanPaused ? 'paused' : 'running');
                        } else if (batch >= CONFIG.BATCH_SIZE) {
                            const p = humanDelay(CONFIG.BATCH_PAUSE_MIN, CONFIG.BATCH_PAUSE_MAX);
                            addLog(t('logBatchPause', batch, fmtDur(p)), 'info');
                            setPhase(t('phaseBatchPause', fmtDur(p)), 'paused'); await sleep(p); batch = 0;
                            if (state.running) setPhase(state.scanPaused ? t('phasePaused') : t('phaseResuming'), state.scanPaused ? 'paused' : 'running');
                        } else {
                            // Gaussian delay with adaptive multiplier
                            let delay = humanDelay(CONFIG.REMOVE_DELAY_MIN, CONFIG.REMOVE_DELAY_MAX);
                            delay = Math.round(delay * rtMult);
                            if (rtMult > 1.0) {
                                setPhase(t('phaseAdaptive', rtMult.toFixed(1)), 'adaptive');
                            }
                            await sleep(delay);
                            if (state.running && rtMult <= 1.0) {
                                setPhase(state.scanPaused ? t('phasePaused') : (state.fetchComplete ? t('phaseRemoving') : t('phaseScanning')), state.scanPaused ? 'paused' : 'running');
                            }
                        }
                    } else {
                        state.removalQueue.shift(); queueIds.delete(bot.id); failedIds.add(bot.id);
                        state.failedList.push({ id: bot.id, username: bot.username, status: r.status });
                        addLog(t('logRemoveFailed', bot.username, r.status), 'err');
                        state.consecutiveErrors++;
                        if (state.consecutiveErrors >= CONFIG.MAX_ERRORS) { addLog(t('logConsecutiveErrors', CONFIG.MAX_ERRORS, fmtDur(CONFIG.ERROR_PAUSE)), 'err'); await sleep(CONFIG.ERROR_PAUSE); state.consecutiveErrors = 0; }
                        ui_update();
                    }
                } catch (e) { addLog(t('logError', e.message), 'err'); state.consecutiveErrors++; if (state.consecutiveErrors >= CONFIG.MAX_ERRORS) { await sleep(CONFIG.ERROR_PAUSE); state.consecutiveErrors = 0; } }
            }
            saveState(); renderBotList();
        }
    
        // ═══ BACKOFF (with jitter for unpredictability) ═══
        async function handleBackoff() {
            // Add ±20% jitter to backoff time so it's not perfectly predictable
            const jitter = state.currentBackoff * (0.8 + Math.random() * 0.4);
            const w = Math.round(jitter);
            addLog(t('logBackoff', '', fmtDur(w)), 'warn');
            await sleep(w);
            state.currentBackoff = Math.min(state.currentBackoff * CONFIG.RATE_LIMIT_MULT, CONFIG.RATE_LIMIT_MAX);
        }
        function resetBackoff() { state.currentBackoff = CONFIG.RATE_LIMIT_INITIAL; state.consecutiveErrors = 0; }
    
        // ═══ INIT ═══
        injectCSS(); buildUI();
        addLog(t('logReady'), 'info');
        renderBotList(); setButtons('idle');
        setPhase(t('phaseReady'));
    })();
  • 07-06-2026, 02:05:28
    #3
    soylenmezsmt adlı üyeden alıntı: mesajı görüntüle
    Arkadaşlar meraklısına kodu inceledim.
    Verilerinizi herhangibi yere çalmıyor tamamen localde instagram apilerine istek atıyor.
    • Profil fotoğrafı yoksa: +4 puan.
    • Kullanıcı adında çok fazla rakam varsa: +3 puan.
    • Tam adı yoksa veya sadece kullanıcı adından oluşuyorsa: +1/2 puan.
    • Onaylı (mavi tikli) hesapsa: -10 puan (bot olamaz).
    olarak değerlendiriyor ve buna göre takipçileri çıkarıyor. Rate limitleri pek incelemedim hesabınızda çok bot varsa geçici/kalıcı engellemelere girebilirsiniz.
    Elinize sağlık hocam ben de ihtiyacı olan kullansın diye paylaşmak istedim