Cam ARNA

Multi-archive search tool with modern dashboard design + Import/Export + History (Live Search)

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         Cam ARNA
// @namespace    http://tampermonkey.net/
// @version      2.5
// @description  Multi-archive search tool with modern dashboard design + Import/Export + History (Live Search)
// @author       user006-ui
// @license      MIT
// @match        https://*.stripchat.com/*
// @match        https://*.chaturbate.com/*
// @match        https://chaturbate.com/*
// @match        https://*.bongacams.com/*
// @match        https://bongacams.com/*
// @require      https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.1.1/crypto-js.min.js
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_xmlhttpRequest
// @grant        GM_addStyle
// @grant        GM_openInTab
// @connect      archivebate.com
// @connect      showcamrips.com
// @connect      camshowrecordings.com
// @connect      camwh.com
// @connect      topcamvideos.com
// @connect      lovecamporn.com
// @connect      camwhores.tv
// @connect      bestcam.tv
// @connect      xhomealone.com
// @connect      stream-leak.com
// @connect      mfcamhub.com
// @connect      camshowrecord.net
// @connect      camwhoresbay.com
// @connect      camsave1.com
// @connect      onscreens.me
// @connect      livecamrips.to
// @connect      cumcams.cc
// @connect      allmy.cam
// @connect      livecamsrip.com
// @connect      stripchat.com
// @connect      chaturbate.com
// @connect      bongacams.com
// @connect      camgirlfinder.net
// @connect      nrtool.to
// @connect      camsrip.com
// ==/UserScript==

(function() {
    'use strict';

    // --- Configuration & Utils ---
    const Config = {
        version: '2.5',
        maxHistory: 10,
        colors: {
            bg: '#0f172a',
            surface: '#1e293b',
            border: '#334155',
            primary: '#6366f1',
            accent: '#818cf8',
            text: '#f8fafc',
            textMuted: '#94a3b8',
            success: '#10b981',
            error: '#ef4444'
        }
    };

    const Utils = {
        escapeRegex: (str) => str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'),

        isValidUsername: (str) => /^[a-zA-Z0-9_\-]{3,50}$/.test(str),
        openSafe: (url) => {
            if (typeof GM_openInTab === 'function') {
                GM_openInTab(url, { active: true, insert: true, setParent: true });
            } else {
                window.open(url, '_blank', 'noopener,noreferrer');
            }
        },
        getCurrentPlatform: () => {
            const h = window.location.host;
            if (h.includes('chaturbate')) return 'CB';
            if (h.includes('stripchat')) return 'SC';
            if (h.includes('bongacams')) return 'BC';
            return 'Other';
        },
        crypto: {
            secret: 'CAM_ARNA_SALT_v3_MODERN',
            encrypt: (text) => {
                if (!text) return '';
                try { return CryptoJS.AES.encrypt(text, Utils.crypto.secret).toString(); } catch (e) { return ''; }
            },
            decrypt: (ciphertext) => {
                if (!ciphertext) return '';
                try {
                    const bytes = CryptoJS.AES.decrypt(ciphertext, Utils.crypto.secret);
                    return bytes.toString(CryptoJS.enc.Utf8);
                } catch (e) { return ''; }
            }
        }
    };

    const Storage = {
        get: (key, defaultValue) => GM_getValue(key, defaultValue),
        set: (key, value) => GM_setValue(key, value)
    };

    // --- Sites Configuration ---
    const archiveSites = [
        { name: 'Archivebate', url: 'https://archivebate.com/profile/{username}', domain: 'archivebate.com' },
        { name: 'Showcamrips', url: 'https://showcamrips.com/model/en/{username}', domain: 'showcamrips.com' },
        { name: 'CamRecordings', url: 'https://www.camshowrecordings.com/model/{username}', domain: 'camshowrecordings.com' },
        { name: 'CamWH', url: 'https://camwh.com/tags/{username}/', domain: 'camwh.com' },
        { name: 'TopCam', url: 'https://www.topcamvideos.com/showall/?search={username}', domain: 'topcamvideos.com' },
        { name: 'LoveCam', url: 'https://lovecamporn.com/showall/?search={username}', domain: 'lovecamporn.com' },
        { name: 'Camwhores.tv', url: 'https://www.camwhores.tv/search/{username}/', domain: 'camwhores.tv' },
        { name: 'Bestcam', url: 'https://bestcam.tv/model/{username}', domain: 'bestcam.tv' },
        { name: 'XHome', url: 'https://xhomealone.com/tags/{username}/', domain: 'xhomealone.com' },
        { name: 'StreamLeak', url: 'https://stream-leak.com/models/{username}/', domain: 'stream-leak.com' },
        { name: 'MFCamHub', url: 'https://mfcamhub.com/models/{username}/', domain: 'mfcamhub.com' },
        { name: 'CamRecord', url: 'https://camshowrecord.net/video/list?page=1&model={username}', domain: 'camshowrecord.net' },
        { name: 'CW Bay', url: 'https://www.camwhoresbay.com/search/{username}/', domain: 'camwhoresbay.com' },
        { name: 'CamSave', url: 'https://www.camsave1.com/?search={username}&women=true', domain: 'camsave1.com' },
        { name: 'OnScreens', url: 'https://www.onscreens.me/m/{username}', domain: 'onscreens.me' },
        { name: 'LiveCamRips', url: 'https://livecamrips.to/search/{username}/1', domain: 'livecamrips.to' },
        { name: 'CumCams', url: 'https://cumcams.cc/performer/{username}', domain: 'cumcams.cc' },
        { name: 'AllMyCam', url: 'https://allmy.cam/search/{username}/', domain: 'allmy.cam' },
        { name: 'LCRip', url: 'https://www.livecamsrip.com/{username}/profile', domain: 'livecamsrip.com' },
        { name: 'CamsRip', url: 'https://camsrip.com/{username}/profile', domain: 'camsrip.com' }
    ];

    function getFaviconUrl(domain) {
        return `https://www.google.com/s2/favicons?domain=${domain}&sz=32`;
    }

    // --- Page Checker Logic ---
    const PageChecker = {
        checkPage: function(url) {
            return new Promise((resolve) => {
                try {
                    GM_xmlhttpRequest({
                        method: 'GET',
                        url: url,
                        timeout: 10000,
                        onload: (res) => {
                            try { resolve(this.analyze(res, url)); }
                            catch(e) { resolve(false); }
                        },
                        onerror: () => resolve(false),
                        ontimeout: () => resolve(false)
                    });
                } catch(e) {
                    resolve(false);
                }
            });
        },
        analyze: function(response, url) {
            try {
                if (!response || response.status === 404 || response.status >= 500) return false;
                const text = response.responseText;
                if (!text || typeof text !== 'string') return false;

                const lowerText = text.toLowerCase();
                const titleMatch = lowerText.match(/<title[^>]*>(.*?)<\/title>/i);
                const title = titleMatch ? titleMatch[1] : '';

                // Spezielle Platform-Regeln
                if (url.includes('livecamrips.to')) {
                    const noResultPatterns = ['no records found', 'no models found', 'no results', '0 models found'];
                    if (noResultPatterns.some(p => lowerText.includes(p))) return false;
                    if (!text.includes('class="video"') && !text.includes('model-card')) return false;
                }
                if (url.includes('cumcams.cc')) {
                    const notFoundPatterns = [ /<h1[^>]*>404<\/h1>/i, /performer\s*not\s*found/i ];
                    if (notFoundPatterns.some(p => p.test(text))) return false;
                    if (!text.includes('profile-info') && !text.includes('class="performer"')) return false;
                }
                if (url.includes('allmy.cam')) {
                    if (!text.includes('class="video-card"')) return false;
                }
                if (url.includes('showcamrips')) {
                    if (text.includes('data:image/png;base64')) return false;
                }
                if (url.includes('camshowrecordings.com')) {
                    if (!text.includes('class="h1modelpage"')) return false;
                }

                // --- LCRip / livecamsrip.com Logik ---
                if (url.includes('livecamsrip.com')) {
                    if (lowerText.includes('no records found')) return false;
                }

                // --- Camwhores.tv & CW Bay Logik ---
                if (url.includes('camwhores.tv') || url.includes('camwhoresbay.com')) {
                    if (lowerText.includes('there is no data in this list.')) return false;
                    if (/no\s*videos?\s*found|0\s*videos/i.test(lowerText)) return false;
                }

                if (['not found', '404', 'error'].some(term => title.includes(term))) return false;
                const genericNotFound = [/no\s*videos?\s*found/i, /no\s*results?\s*found/i, /does\s*not\s*exist/i, /\b0\s*results?\b/i];
                if (genericNotFound.some(p => p.test(lowerText))) return false;

                return true;
            } catch(e) {
                return false;
            }
        }
    };

    // --- Styling ---
    function injectStyles() {
        GM_addStyle(`
            :root {
                --ca-bg: ${Config.colors.bg};
                --ca-surf: ${Config.colors.surface};
                --ca-border: ${Config.colors.border};
                --ca-prim: ${Config.colors.primary};
                --ca-acc: ${Config.colors.accent};
                --ca-text: ${Config.colors.text};
                --ca-muted: ${Config.colors.textMuted};
                --ca-success: ${Config.colors.success};
                --ca-error: ${Config.colors.error};
            }
            .ca-fab {
                position: fixed; bottom: 20px; right: 20px;
                width: 50px; height: 50px; border-radius: 50%;
                background: var(--ca-prim); color: white; border: none;
                cursor: pointer; z-index: 9999; font-size: 20px;
                box-shadow: 0 4px 12px rgba(0,0,0,0.3);
                display: flex; align-items: center; justify-content: center;
            }
            .ca-badge {
                position: absolute; top: -5px; right: -5px;
                background: var(--ca-error); color: white;
                font-size: 11px; font-weight: bold;
                padding: 2px 6px; border-radius: 10px;
                display: none; box-shadow: 0 2px 4px rgba(0,0,0,0.2);
            }
            .ca-overlay {
                position: fixed; inset: 0; background: rgba(0,0,0,0.7);
                display: flex; align-items: center; justify-content: center;
                z-index: 10000; font-family: sans-serif;
            }
            .ca-panel {
                background: var(--ca-bg); width: 450px; max-height: 80vh;
                border-radius: 12px; border: 1px solid var(--ca-border);
                display: flex; flex-direction: column; overflow: hidden; color: var(--ca-text);
            }
            .ca-head { padding: 16px; border-bottom: 1px solid var(--ca-border); display: flex; justify-content: space-between; align-items: center; }
            .ca-brand { font-weight: bold; font-size: 18px; display: flex; align-items: center; gap: 8px; }
            .ca-tabs { display: flex; background: var(--ca-surf); padding: 4px; gap: 4px; flex-wrap: wrap; }
            .ca-tab { flex: 1; padding: 8px; border: none; background: none; color: var(--ca-muted); cursor: pointer; border-radius: 6px; font-size: 13px; }
            .ca-tab.active { background: var(--ca-prim); color: white; }
            .ca-body { padding: 16px; overflow-y: auto; display: flex; flex-direction: column; flex: 1; }
            .ca-search-box { position: relative; margin-bottom: 16px; }
            .ca-input { width: 100%; padding: 10px 10px 10px 35px; background: var(--ca-surf); border: 1px solid var(--ca-border); border-radius: 8px; color: white; box-sizing: border-box; }
            .ca-icon { position: absolute; left: 10px; top: 10px; width: 18px; color: var(--ca-muted); }
            .ca-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; }
            .ca-item { background: var(--ca-surf); padding: 10px; border-radius: 8px; border: 1px solid var(--ca-border); display: flex; align-items: center; gap: 10px; cursor: pointer; transition: 0.2s; position: relative; }
            .ca-item:hover { border-color: var(--ca-prim); }
            .ca-item img { width: 16px; height: 16px; }
            .ca-item-name { font-size: 13px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
            .ca-status { width: 8px; height: 8px; border-radius: 50%; margin-left: auto; }
            .checking .ca-status { background: orange; animation: pulse 1s infinite; }
            .found { border-color: var(--ca-success) !important; }
            .found .ca-status { background: var(--ca-success); }
            .not-found { opacity: 0.5; filter: grayscale(1); }
            .not-found .ca-status { background: var(--ca-error); }
            .ca-toast { position: fixed; bottom: 80px; right: 20px; background: var(--ca-prim); padding: 8px 16px; border-radius: 4px; color: white; z-index: 10001; }
            .ca-close { background: none; border: none; color: var(--ca-muted); cursor: pointer; font-size: 18px; }
            .ca-btn-small { padding: 4px 8px; background: var(--ca-surf); border: 1px solid var(--ca-border); border-radius: 4px; color: var(--ca-text); cursor: pointer; font-size: 12px; }
            .ca-btn-small:hover { background: var(--ca-prim); border-color: var(--ca-prim); }
            .ca-tag { font-size: 10px; background: var(--ca-border); padding: 2px 6px; border-radius: 10px; color: var(--ca-text); }
            .ca-history-item { padding: 8px; border-bottom: 1px solid var(--ca-border); display: flex; justify-content: space-between; align-items: center; cursor: pointer; }
            .ca-history-item:hover { background: var(--ca-surf); }
            .ca-site-toggle { display: flex; justify-content: space-between; align-items: center; padding: 8px; border-bottom: 1px solid var(--ca-border); }
            @keyframes pulse { 0% { opacity: 0.5; } 50% { opacity: 1; } 100% { opacity: 0.5; } }
        `);
    }

    // --- Main UI Logic ---
    const UI = {
        isOpen: false,

        toggle: function() {
            if (this.isOpen) {
                document.querySelector('.ca-overlay')?.remove();
                this.isOpen = false;
            } else {
                this.render();
                this.isOpen = true;
                const activeUser = document.getElementById('ca-user').value.trim();
                if (activeUser) document.getElementById('ca-user').select();
            }
        },

        render: function() {
            const overlay = document.createElement('div');
            overlay.className = 'ca-overlay';
            overlay.onclick = (e) => { if(e.target === overlay) UI.toggle(); };

            const panel = document.createElement('div');
            panel.className = 'ca-panel';
            panel.innerHTML = `
                <div class="ca-head">
                    <div class="ca-brand">
                        <span>ARNA</span>
                        <span style="font-size:10px; color:var(--ca-muted); border:1px solid var(--ca-border); padding:1px 4px; border-radius:4px;">${Config.version}</span>
                    </div>
                    <button class="ca-close">✕</button>
                </div>
                <div class="ca-tabs">
                    <button class="ca-tab active" data-view="search">Search</button>
                    <button class="ca-tab" data-view="history">History</button>
                    <button class="ca-tab" data-view="saved">Saved</button>
                    <button class="ca-tab" data-view="tools">Tools</button>
                </div>
                <div class="ca-body">
                    <div class="ca-search-box">
                        <svg class="ca-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M20 21l-4.35-4.35m1.35-5.65a7 7 0 11-14 0 7 7 0 0114 0z"></path></svg>
                        <input type="text" class="ca-input" id="ca-user" placeholder="Search username..." autocomplete="off">
                    </div>

                    <div id="view-search">
                        <div style="font-size: 12px; color: var(--ca-muted); margin-bottom: 8px; display: flex; justify-content: space-between;">
                            <span>Archives / Recorders</span>
                            <span id="ca-counter">0 / 0 found</span>
                        </div>
                        <div class="ca-grid" id="ca-archives"></div>
                        <div class="ca-item" id="ca-btn-save" style="justify-content:center; border-style:dashed; opacity:0.8; margin-top: 12px;">
                            <span class="ca-item-name">Save to Favorites</span>
                        </div>
                    </div>

                    <div id="view-history" style="display:none;">
                        <div style="font-size: 12px; color: var(--ca-muted); margin-bottom: 8px;">Recent Searches</div>
                        <div id="ca-history-list"></div>
                    </div>

                    <div id="view-saved" style="display:none;">
                        <div style="display:flex; gap:8px; margin-bottom:12px;">
                            <button id="ca-btn-import" class="ca-btn-small" style="flex:1;">Import JSON</button>
                            <button id="ca-btn-export" class="ca-btn-small" style="flex:1;">Export JSON</button>
                            <input type="file" id="ca-file-input" style="display:none;" accept=".json">
                        </div>
                        <div id="ca-saved-list"></div>
                    </div>

                    <div id="view-tools" style="display:none;">
                        <div style="font-size: 12px; color: var(--ca-muted); margin-bottom: 8px;">Quick Links</div>
                        <div class="ca-grid">
                            <div class="ca-item tool-btn" data-url="https://www.cbhours.com/user/{u}.html"><span class="ca-item-name">Schedule</span></div>
                            <div class="ca-item tool-btn" data-url="https://statbate.com/search/1/{u}"><span class="ca-item-name">Statistics</span></div>
                            <div class="ca-item tool-btn" data-url="https://camgirlfinder.net/models/sc/{u}"><span class="ca-item-name">Finder</span></div>
                            <div class="ca-item tool-btn" data-url="https://nrtool.to/nrtool/search?site=&s={u}"><span class="ca-item-name">Images</span></div>
                        </div>
                        <div style="font-size: 12px; color: var(--ca-muted); margin: 16px 0 8px 0;">Manage Sites</div>
                        <div id="ca-site-toggles" style="max-height: 150px; overflow-y: auto; background: var(--ca-surf); border-radius: 8px; border: 1px solid var(--ca-border);"></div>
                    </div>
                </div>
            `;
            overlay.appendChild(panel);
            document.body.appendChild(overlay);

            this.initArchivesGrid(panel);
            this.initSiteToggles(panel);
            this.setupEventListeners(panel, overlay);

            // Auto detect
            const input = panel.querySelector('#ca-user');
            this.detectUser(input);
        },

        initArchivesGrid: function(panel) {
            const grid = panel.querySelector('#ca-archives');
            grid.innerHTML = '';
            const disabledSites = Storage.get('ca_disabled_sites', []);

            archiveSites.forEach(site => {
                if (disabledSites.includes(site.name)) return;

                const el = document.createElement('div');
                el.className = 'ca-item archive-item';
                el.dataset.url = site.url;
                el.dataset.name = site.name;
                el.innerHTML = `
                    <img src="${getFaviconUrl(site.domain)}" loading="lazy">
                    <span class="ca-item-name">${site.name}</span>
                    <div class="ca-status"></div>
                `;
                el.onclick = () => {
                    const u = document.getElementById('ca-user').value.trim();
                    if (u) Utils.openSafe(site.url.replace('{username}', u));
                };
                grid.appendChild(el);
            });
        },

        initSiteToggles: function(panel) {
            const container = panel.querySelector('#ca-site-toggles');
            let disabledSites = Storage.get('ca_disabled_sites', []);

            archiveSites.forEach(site => {
                const row = document.createElement('div');
                row.className = 'ca-site-toggle';
                const isChecked = !disabledSites.includes(site.name);
                row.innerHTML = `
                    <span style="font-size: 13px;">${site.name}</span>
                    <input type="checkbox" ${isChecked ? 'checked' : ''}>
                `;
                row.querySelector('input').onchange = (e) => {
                    if (e.target.checked) {
                        disabledSites = disabledSites.filter(n => n !== site.name);
                    } else {
                        if (!disabledSites.includes(site.name)) disabledSites.push(site.name);
                    }
                    Storage.set('ca_disabled_sites', disabledSites);
                    this.initArchivesGrid(document.querySelector('.ca-panel'));
                };
                container.appendChild(row);
            });
        },

        setupEventListeners: function(panel, overlay) {
            panel.querySelector('.ca-close').onclick = () => UI.toggle();

            const tabs = panel.querySelectorAll('.ca-tab');
            tabs.forEach(t => {
                t.onclick = () => {
                    tabs.forEach(x => x.classList.remove('active'));
                    t.classList.add('active');
                    panel.querySelectorAll('div[id^="view-"]').forEach(v => v.style.display = 'none');
                    panel.querySelector(`#view-${t.dataset.view}`).style.display = 'block';

                    if(t.dataset.view === 'saved') UI.loadSaved();
                    if(t.dataset.view === 'history') UI.loadHistory();
                };
            });

            const input = panel.querySelector('#ca-user');
            let debounce;
            input.oninput = () => {
                clearTimeout(debounce);
                debounce = setTimeout(() => UI.checkAll(input.value.trim()), 600);
            };

            panel.querySelector('#ca-btn-save').onclick = () => {
                const u = input.value.trim();
                if (u && Utils.isValidUsername(u)) {
                    let current = Storage.get('ca_saved', []);
                    current = current.map(item => typeof item === 'string' ? { user: item, platform: 'Unknown' } : item);

                    if (!current.find(i => i.user === u)) {
                        current.push({ user: u, platform: Utils.getCurrentPlatform() });
                        Storage.set('ca_saved', current);
                        UI.showToast(`Saved ${u}`);
                    } else {
                        UI.showToast(`${u} already saved`);
                    }
                }
            };

            const fileInput = panel.querySelector('#ca-file-input');
            panel.querySelector('#ca-btn-export').onclick = () => {
                const saved = Storage.get('ca_saved', []);
                if(saved.length === 0) return UI.showToast('Nothing to export');
                const blob = new Blob([JSON.stringify(saved, null, 2)], { type: 'application/json' });
                const url = URL.createObjectURL(blob);
                const a = document.createElement('a');
                a.href = url;
                a.download = `arna_backup_${new Date().toISOString().slice(0,10)}.json`;
                a.click();
                URL.revokeObjectURL(url);
                UI.showToast('Export successful');
            };

            panel.querySelector('#ca-btn-import').onclick = () => fileInput.click();
            fileInput.onchange = (e) => {
                const file = e.target.files[0];
                if(!file) return;
                const reader = new FileReader();
                reader.onload = (ev) => {
                    try {
                        const list = JSON.parse(ev.target.result);
                        if(Array.isArray(list)) {
                            let current = Storage.get('ca_saved', []);
                            current = current.map(item => typeof item === 'string' ? { user: item, platform: 'Unknown' } : item);
                            const newItems = list.map(x => typeof x === 'string' ? { user: x, platform: 'Imported' } : x)
                                                 .filter(x => Utils.isValidUsername(x.user) && !current.find(c => c.user === x.user));
                            Storage.set('ca_saved', [...current, ...newItems]);
                            UI.showToast(`Imported ${newItems.length} profiles`);
                            UI.loadSaved();
                        } else {
                            UI.showToast('Invalid JSON format');
                        }
                    } catch(err) {
                        UI.showToast('Error reading file');
                    }
                };
                reader.readAsText(file);
                fileInput.value = '';
            };

            panel.querySelectorAll('.tool-btn').forEach(btn => {
                btn.onclick = () => {
                    const u = input.value.trim();
                    if(u) Utils.openSafe(btn.dataset.url.replace('{u}', u));
                };
            });
        },

        checkAll: async function(username) {
            if (!username || !Utils.isValidUsername(username)) return;

            try {
                let history = Storage.get('ca_history', []);
                history = history.filter(u => u !== username);
                history.unshift(username);
                if (history.length > Config.maxHistory) history = history.slice(0, Config.maxHistory);
                Storage.set('ca_history', history);
                const historyView = document.getElementById('view-history');
                if (historyView && historyView.style.display === 'block') this.loadHistory();
            } catch(e) {}

            const items = Array.from(document.querySelectorAll('.archive-item'));
            if (items.length === 0) return;

            items.forEach(el => {
                el.classList.remove('found', 'not-found');
                el.classList.add('checking');
            });

            const counterEl = document.getElementById('ca-counter');
            const totalItems = items.length;
            let foundCount = 0;
            if (counterEl) counterEl.innerText = `0 / ${totalItems} found`;

            const updateUI = (el, isFound) => {
                try {
                    el.classList.remove('checking');
                    if (isFound) {
                        el.classList.add('found');
                        foundCount++;
                    } else {
                        el.classList.add('not-found');
                    }
                    if (counterEl) counterEl.innerText = `${foundCount} / ${totalItems} found`;

                    const badge = document.getElementById('ca-fab-badge');
                    if (badge) {
                        badge.innerText = foundCount;
                        badge.style.display = foundCount > 0 ? 'flex' : 'none';
                    }
                } catch(e) {}
            };

            items.forEach(async (el) => {
                let exists = false;
                try {
                    const url = el.dataset.url.replace('{username}', username);
                    exists = await PageChecker.checkPage(url);
                } catch(e) {}
                updateUI(el, exists);
            });
        },

        loadSaved: function() {
            let list = Storage.get('ca_saved', []);
            list = list.map(item => typeof item === 'string' ? { user: item, platform: '?' } : item);
            const container = document.getElementById('ca-saved-list');
            container.innerHTML = '';

            if (list.length === 0) {
                container.innerHTML = '<div style="text-align:center; padding:20px; color:var(--ca-muted)">No saved profiles</div>';
                return;
            }

            list.forEach(item => {
                const div = document.createElement('div');
                div.className = 'ca-item';
                div.style.justifyContent = 'space-between';
                div.innerHTML = `
                    <div style="display:flex; align-items:center; gap:8px;">
                        <span style="font-weight:600">${item.user}</span>
                        <span class="ca-tag">${item.platform || '?'}</span>
                    </div>
                    <button class="ca-delete" style="border:none;background:none;cursor:pointer;color:var(--ca-muted);">✕</button>
                `;
                div.onclick = (e) => {
                    if (!e.target.classList.contains('ca-delete')) {
                        document.getElementById('ca-user').value = item.user;
                        document.querySelector('[data-view="search"]').click();
                        UI.checkAll(item.user);
                    }
                };
                div.querySelector('.ca-delete').onclick = (e) => {
                    e.stopPropagation();
                    const newList = list.filter(x => x.user !== item.user);
                    Storage.set('ca_saved', newList);
                    UI.loadSaved();
                };
                container.appendChild(div);
            });
        },

        loadHistory: function() {
            const history = Storage.get('ca_history', []);
            const container = document.getElementById('ca-history-list');
            container.innerHTML = '';

            if (history.length === 0) {
                container.innerHTML = '<div style="text-align:center; padding:20px; color:var(--ca-muted)">No history yet</div>';
                return;
            }

            history.forEach(u => {
                const div = document.createElement('div');
                div.className = 'ca-history-item';
                div.innerHTML = `<span style="font-weight:500">${u}</span> <span style="font-size:12px;color:var(--ca-muted)">↺</span>`;
                div.onclick = () => {
                    document.getElementById('ca-user').value = u;
                    document.querySelector('[data-view="search"]').click();
                    UI.checkAll(u);
                };
                container.appendChild(div);
            });
        },

        detectUser: function(input) {
            try {
                const path = window.location.pathname.split('/').filter(Boolean);
                let user = '';
                const host = window.location.host;

                if (host.includes('chaturbate') || host.includes('bongacams')) {
                    user = path[0];
                } else {
                    user = path[path.length - 1];
                }

                const ignoredPaths = ['auth', 'tags', 'couples', 'female', 'myfriends', 'male', 'trans', 'new-models', 'spy-mode'];
                if (user && Utils.isValidUsername(user) && !ignoredPaths.includes(user.toLowerCase())) {
                    input.value = user;
                    UI.checkAll(user);
                }
            } catch(e) {}
        },

        showToast: function(msg) {
            const t = document.createElement('div');
            t.className = 'ca-toast';
            t.innerText = msg;
            document.body.appendChild(t);
            setTimeout(() => t.remove(), 3000);
        }
    };

    function init() {
        injectStyles();
        const fab = document.createElement('div');
        fab.className = 'ca-fab';
        fab.innerHTML = `
            <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"></path></svg>
            <div id="ca-fab-badge" class="ca-badge">0</div>
        `;
        fab.onclick = () => UI.toggle();
        document.body.appendChild(fab);

        document.addEventListener('keydown', (e) => {
            const target = e.target.tagName.toLowerCase();
            if (target === 'input' || target === 'textarea') return;
            if (e.shiftKey && e.key.toLowerCase() === 'a') {
                e.preventDefault();
                UI.toggle();
            }
        });
    }

    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', init);
    } else {
        init();
    }
})();