Cam ARNA

Multi-archive search tool

За да инсталирате този скрипт, трябва да имате инсталирано разширение като Tampermonkey, Greasemonkey или Violentmonkey.

За да инсталирате този скрипт, трябва да инсталирате разширение, като например Tampermonkey .

За да инсталирате този скрипт, трябва да имате инсталирано разширение като Tampermonkey или Violentmonkey.

За да инсталирате този скрипт, трябва да имате инсталирано разширение като Tampermonkey или Userscripts.

За да инсталирате скрипта, трябва да инсталирате разширение като Tampermonkey.

За да инсталирате този скрипт, трябва да имате инсталиран скриптов мениджър.

(Вече имам скриптов мениджър, искам да го инсталирам!)

За да инсталирате този стил, трябва да инсталирате разширение като Stylus.

За да инсталирате този стил, трябва да инсталирате разширение като Stylus.

За да инсталирате този стил, трябва да инсталирате разширение като Stylus.

За да инсталирате този стил, трябва да имате инсталиран мениджър на потребителски стилове.

За да инсталирате този стил, трябва да имате инсталиран мениджър на потребителски стилове.

За да инсталирате този стил, трябва да имате инсталиран мениджър на потребителски стилове.

(Вече имам инсталиран мениджър на стиловете, искам да го инсталирам!)

// ==UserScript==
// @name         Cam ARNA 
// @namespace    http://tampermonkey.net/
// @version      2.1.1
// @description  Multi-archive search tool
// @author       user006-ui 
// @license      MIT
// @match        https://*.stripchat.com/*
// @match        https://*.chaturbate.com/*
// @match        https://chaturbate.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
// ==/UserScript==

(function() {
    'use strict';

    // --- Utils: Security & Validation ---
    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 {
                const newWindow = window.open(url, '_blank', 'noopener,noreferrer');
                if (newWindow) newWindow.opener = null;
            }
        },

        // Simple AES Encryption (Obfuscation for Storage)
        crypto: {
            secret: 'CAM_ARNA_SALT_v2_SECURE', 
            encrypt: (text) => {
                if (!text) return '';
                try {
                    return CryptoJS.AES.encrypt(text, Utils.crypto.secret).toString();
                } catch (e) { console.error("Encrypt Error", 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 ''; } 
            }
        }
    };

    // --- Storage Helper ---
    const Storage = {
        get: (key, defaultValue) => GM_getValue(key, defaultValue),
        set: (key, value) => { GM_setValue(key, value); return true; },
        
        getSecure: (key, defaultValue) => {
            const encrypted = GM_getValue(key, '');
            if (!encrypted) return defaultValue;
            const decrypted = Utils.crypto.decrypt(encrypted);
            return decrypted || defaultValue;
        },
        setSecure: (key, value) => {
            const encrypted = Utils.crypto.encrypt(value);
            GM_setValue(key, encrypted);
        }
    };

    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: 'Camshowrecordings', url: 'https://www.camshowrecordings.com/model/{username}', domain: 'camshowrecordings.com' },
        { name: 'Camwh', url: 'https://camwh.com/tags/{username}/', domain: 'camwh.com' },
        { name: 'TopCamVideos', url: 'https://www.topcamvideos.com/showall/?search={username}', domain: 'topcamvideos.com' },
        { name: 'LoveCamPorn', 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.tv', url: 'https://bestcam.tv/model/{username}', domain: 'bestcam.tv' },
        { name: 'Xhomealone', url: 'https://xhomealone.com/tags/{username}/', domain: 'xhomealone.com' },
        { name: 'Stream-leak', url: 'https://stream-leak.com/models/{username}/', domain: 'stream-leak.com' },
        { name: 'MFCamhub', url: 'https://mfcamhub.com/models/{username}/', domain: 'mfcamhub.com' },
        { name: 'Camshowrecord', url: 'https://camshowrecord.net/video/list?page=1&model={username}', domain: 'camshowrecord.net' },
        { name: 'Camwhoresbay', url: 'https://www.camwhoresbay.com/search/{username}/', domain: 'camwhoresbay.com' },
        { name: 'CamSave1', url: 'https://www.camsave1.com/?feet=0&face=0&ass=0&tits=0&pussy=0&search={username}&women=true&couples=true&men=false&trans=false', 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: 'LiveCamsRip', url: 'https://www.livecamsrip.com/{username}/profile', domain: 'livecamsrip.com' }
    ];

    const mainSites = {
        'stripchat': 'https://stripchat.com/{username}',
        'chaturbate': 'https://chaturbate.com/{username}/'
    };

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

    // --- Page Checker ---
    const PageChecker = {
        cache: new Map(),
        CONSTANTS: {
            TTL: 10 * 60 * 1000, 
            MAX_SIZE: 100
        },

        checkPage: function(url) {
            return new Promise((resolve) => {
                const now = Date.now();
                
                if (this.cache.has(url)) {
                    const entry = this.cache.get(url);
                    if (now - entry.timestamp < this.CONSTANTS.TTL) {
                        resolve(entry.exists);
                        return;
                    } else {
                        this.cache.delete(url);
                    }
                }

                GM_xmlhttpRequest({
                    method: 'GET', url: url, timeout: 8000,
                    onload: (response) => {
                        const exists = this.analyzeResponse(response, url);
                        this.addToCache(url, exists);
                        resolve(exists);
                    },
                    onerror: () => { 
                        this.addToCache(url, false);
                        resolve(false); 
                    }
                });
            }).catch(e => {
                console.warn("PageChecker rejected:", e);
                return false;
            });
        },
        
        addToCache: function(url, exists) {
            if (this.cache.size >= this.CONSTANTS.MAX_SIZE) {
                const firstKey = this.cache.keys().next().value;
                this.cache.delete(firstKey);
            }
            this.cache.set(url, { exists: exists, timestamp: Date.now() });
        },

        analyzeResponse: function(response, url) {
            try {
                if (response.status === 404 || response.status >= 500) return false;
                const text = response.responseText;
                if (!text) return false;
                const lowerText = text.toLowerCase();

                if (url.includes('showcamrips') && text.includes("data:image/png;base64,iVBORw0KGgo")) return false;

                if (url.includes('camshowrecordings.com')) {
                    if (text.includes('class="h1modelindex"')) return false;
                    const userMatch = url.match(/\/model\/([^/?#]+)/);
                    if (userMatch && userMatch[1]) {
                        const username = decodeURIComponent(userMatch[1]).toLowerCase();
                        if (text.includes('class="h1modelpage"') && lowerText.includes(username)) return true;
                        return false;
                    }
                    return text.includes('class="h1modelpage"');
                }

                if (url.includes('camwhores.tv')) {
                    if (lowerText.includes('no videos found') || lowerText.includes('no results')) return false;
                    const userMatch = url.match(/\/search\/([^/?#]+)/);
                    if (userMatch) {
                        const rawUser = decodeURIComponent(userMatch[1]);
                        const safeUserRegex = Utils.escapeRegex(rawUser).replace(/[\-\_]/g, '[\\s\\-\\_]+');
                        const titleRegex = new RegExp(`class=["']title["'][^>]*>\\s*[^<]*${safeUserRegex}`, 'i');
                        const linkRegex = new RegExp(`href=["'].*?\/videos\/\\d+\/.*?${safeUserRegex}`, 'i');
                        return (titleRegex.test(text) || linkRegex.test(text));
                    }
                }

                if (url.includes('camwhoresbay.com')) {
                    const userMatch = url.match(/\/search\/([^/?#]+)/);
                    if (userMatch) {
                        const rawUser = decodeURIComponent(userMatch[1]);
                        const safeUserRegex = Utils.escapeRegex(rawUser).replace(/[\-\_]/g, '[\\s\\-\\_]*');
                        const videoLinkRegex = new RegExp(`href=["'][^"']*\\/videos\\/[^"']*${safeUserRegex}`, 'i');
                        const titleRegex = new RegExp(`title=["'][^"']*${safeUserRegex}[^"']*["']`, 'i');
                        return (videoLinkRegex.test(text) || titleRegex.test(text));
                    }
                }

                if (url.includes('camshowrecord.net') && text.includes('Sorry, no video found for this')) return false;
                if (url.includes('cumcams.cc') && lowerText.includes('performer not found')) return false;
                if (url.includes('livecamsrip.com') && text.includes('No records found')) return false;

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

                const notFoundTerms = ['no videos found', 'no results found', 'does not exist', 'no videos to show'];
                if (['not found', '404', 'page not found', 'no results'].some(x => title.includes(x))) return false;
                if (notFoundTerms.some(x => lowerText.includes(x))) return false;

                return true;
            } catch(e) {
                console.error("Analysis Error", e);
                return false;
            }
        },
        clearCache: function() { this.cache.clear(); }
    };

    // --- UI & Styles ---
    function injectStyles() {
        GM_addStyle(`
            :root {
                --cam-bg: #09090b; --cam-surface: #18181b; --cam-border: #27272a;
                --cam-primary: #3b82f6; --cam-primary-hover: #2563eb;
                --cam-text: #f4f4f5; --cam-text-muted: #a1a1aa;
                --cam-danger: #ef4444; --cam-success: #22c55e;
                --cam-radius: 16px;
                --cam-font: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
            }
            @keyframes slideUpMobile { from { transform: translateY(100%); opacity: 0; } to { transform: translateY(0); opacity: 1; } }
            @keyframes scaleInDesktop { from { transform: translate(-50%, -45%) scale(0.95); opacity: 0; } to { transform: translate(-50%, -50%) scale(1); opacity: 1; } }
            .cam-backdrop { position: fixed; inset: 0; background: rgba(0, 0, 0, 0.6); backdrop-filter: blur(4px); z-index: 99999; }
            .cam-container {
                position: fixed; z-index: 100000; background: var(--cam-bg); color: var(--cam-text);
                font-family: var(--cam-font); box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.5);
                border: 1px solid var(--cam-border); display: flex; flex-direction: column; overflow: hidden;
            }
            @media (min-width: 601px) {
                .cam-container {
                    top: 50%; left: 50%; transform: translate(-50%, -50%);
                    width: 450px; max-height: 85vh; border-radius: var(--cam-radius);
                    animation: scaleInDesktop 0.2s cubic-bezier(0.16, 1, 0.3, 1);
                }
            }
            @media (max-width: 600px) {
                .cam-container {
                    bottom: 0; left: 0; right: 0; width: 100%; max-height: 90vh;
                    border-radius: 24px 24px 0 0; border-bottom: none;
                    animation: slideUpMobile 0.3s cubic-bezier(0.16, 1, 0.3, 1);
                }
            }
            .cam-header { padding: 16px 20px; background: var(--cam-surface); border-bottom: 1px solid var(--cam-border); display: flex; justify-content: space-between; align-items: center; flex-shrink: 0; }
            .cam-title { font-size: 18px; font-weight: 700; display: flex; align-items: center; gap: 8px; }
            .cam-close { background: none; border: none; color: var(--cam-text-muted); font-size: 24px; cursor: pointer; padding: 4px; border-radius: 50%; transition: 0.2s; display: flex; align-items: center; justify-content: center; width: 32px; height: 32px; }
            .cam-close:hover { background: rgba(255,255,255,0.1); color: #fff; }
            .cam-nav { padding: 12px 20px 0; display: flex; gap: 4px; border-bottom: 1px solid var(--cam-border); background: var(--cam-bg); flex-shrink: 0; }
            .cam-nav-item { flex: 1; padding: 10px; text-align: center; background: none; border: none; color: var(--cam-text-muted); font-size: 14px; font-weight: 600; cursor: pointer; position: relative; transition: 0.2s; }
            .cam-nav-item:hover { color: var(--cam-text); }
            .cam-nav-item.active { color: var(--cam-primary); }
            .cam-nav-item.active::after { content: ''; position: absolute; bottom: 0; left: 0; right: 0; height: 2px; background: var(--cam-primary); border-radius: 2px 2px 0 0; }
            .cam-body { padding: 20px; overflow-y: auto; overscroll-behavior: contain; flex-grow: 1; }
            .cam-input-wrap { margin-bottom: 20px; position: relative; }
            .cam-input { width: 100%; box-sizing: border-box; background: var(--cam-surface); border: 1px solid var(--cam-border); border-radius: 12px; padding: 14px 14px 14px 44px; color: #fff; font-size: 16px; transition: border-color 0.2s; }
            .cam-input:focus { outline: none; border-color: var(--cam-primary); }
            .cam-input-icon { position: absolute; left: 14px; top: 50%; transform: translateY(-50%); color: var(--cam-text-muted); pointer-events: none; }
            .cam-section { margin-bottom: 24px; }
            .cam-section-head { font-size: 13px; text-transform: uppercase; letter-spacing: 0.5px; color: var(--cam-text-muted); margin-bottom: 12px; font-weight: 700; display: flex; justify-content: space-between; align-items: center; }
            .cam-card { background: var(--cam-surface); border: 1px solid var(--cam-border); border-radius: 12px; overflow: hidden; }
            .cam-btn { width: 100%; display: flex; align-items: center; gap: 12px; padding: 14px 16px; background: none; border: none; color: var(--cam-text); font-size: 15px; font-weight: 500; text-align: left; cursor: pointer; transition: background 0.2s; border-bottom: 1px solid var(--cam-border); }
            .cam-btn:last-child { border-bottom: none; }
            .cam-btn:hover { background: rgba(255,255,255,0.05); }
            .cam-btn-primary { background: var(--cam-primary); color: #fff; border: none; justify-content: center; font-weight: 600; border-radius: 12px; margin-top: 10px; width: 100%; padding: 14px; cursor: pointer; }
            .cam-btn-primary:hover { background: var(--cam-primary-hover); }
            .cam-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; }
            .cam-grid-btn { background: var(--cam-surface); border: 1px solid var(--cam-border); border-radius: 10px; padding: 12px; display: flex; align-items: center; gap: 10px; color: var(--cam-text); cursor: pointer; font-size: 13px; font-weight: 500; transition: 0.2s; }
            .cam-grid-btn:hover { border-color: var(--cam-text-muted); transform: translateY(-1px); }
            .cam-grid-btn img { width: 16px; height: 16px; border-radius: 4px; }
            .cam-grid-btn.unavailable { opacity: 0.4; pointer-events: none; filter: grayscale(1); }
            .cam-grid-btn.checking { opacity: 0.7; pointer-events: none; }
            .cam-toast { position: fixed; bottom: 30px; left: 50%; transform: translateX(-50%); padding: 12px 24px; background: #fff; color: #000; font-weight: 600; border-radius: 50px; box-shadow: 0 10px 30px rgba(0,0,0,0.3); z-index: 200000; font-size: 14px; animation: slideUpMobile 0.3s; }
            #cam-fab { position: fixed; bottom: 24px; right: 24px; width: 56px; height: 56px; border-radius: 28px; background: var(--cam-primary); color: white; border: none; font-size: 24px; cursor: pointer; box-shadow: 0 8px 24px rgba(59, 130, 246, 0.4); z-index: 99998; transition: transform 0.2s; display: flex; align-items: center; justify-content: center; }
            #cam-fab:active { transform: scale(0.9); }
        `);
    }

    // --- Profile Manager ---
    const ProfileManager = {
        get: () => {
            try { return Storage.get('cam_profiles', []); } catch(e) { return []; }
        },
        add: (name) => {
            if(!Utils.isValidUsername(name)) return false;
            let list = ProfileManager.get();
            if (!list.includes(name)) { list.push(name); Storage.set('cam_profiles', list); return true; }
            return false;
        },
        remove: (name) => {
            let list = ProfileManager.get();
            Storage.set('cam_profiles', list.filter(n => n !== name));
        }
    };

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

        createMenu: function() {
            if (this.isOpen) return;
            this.isOpen = true;

            const backdrop = document.createElement('div');
            backdrop.className = 'cam-backdrop';
            
            const container = document.createElement('div');
            container.className = 'cam-container';

            container.innerHTML = `
                <div class="cam-header">
                    <div class="cam-title"><span>🌟</span> Cam ARNA <span style="font-size:10px; opacity:0.5; margin-left:5px;">v2.1.1</span></div>
                    <button class="cam-close">✕</button>
                </div>
                <div class="cam-nav">
                    <button class="cam-nav-item active" data-tab="search">Search</button>
                    <button class="cam-nav-item" data-tab="saved">Saved</button>
                    <button class="cam-nav-item" data-tab="settings">Settings</button>
                </div>
                <div class="cam-body">
                    <div class="cam-input-wrap">
                        <span class="cam-input-icon">👤</span>
                        <input type="text" class="cam-input" id="cam-user-input" placeholder="Username..." autocomplete="off">
                    </div>
                    <div id="tab-search" class="cam-tab-content">
                        <div class="cam-section">
                            <div class="cam-section-head">Direct Access</div>
                            <div class="cam-grid">
                                <button class="cam-grid-btn main-site" data-site="stripchat" style="border-color: #8b5cf6;">💜 Stripchat</button>
                                <button class="cam-grid-btn main-site" data-site="chaturbate" style="border-color: #f97316;">🧡 Chaturbate</button>
                            </div>
                            <button id="cam-save-btn" class="cam-btn-primary">💾 Save Profile</button>
                        </div>

                        <div class="cam-section">
                            <div class="cam-section-head">Tools & Stats</div>
                            <div class="cam-grid">
                                <button class="cam-grid-btn extra-tool" data-type="schedule" style="border-color: #10b981;">📅 Schedule</button>
                                <button class="cam-grid-btn extra-tool" data-type="stats" style="border-color: #3b82f6;">📊 Statistics</button>
                                <button class="cam-grid-btn extra-tool" data-type="find" style="border-color: #f43f5e;">🔎 Finder</button>
                            </div>
                        </div>

                        <div class="cam-section">
                            <div class="cam-section-head">Archives <span id="archive-status" style="float:right; font-weight:normal; opacity:0.6;"></span></div>
                            <div class="cam-grid" id="archive-grid"></div>
                        </div>
                    </div>
                    <div id="tab-saved" class="cam-tab-content" style="display:none;">
                        <div class="cam-card" id="saved-list"></div>
                        <div id="saved-empty" style="text-align:center; padding:40px; color:var(--cam-text-muted); display:none;">No profiles saved yet.</div>
                    </div>
                    <div id="tab-settings" class="cam-tab-content" style="display:none;">
                        <div id="settings-content"></div>
                    </div>
                </div>
            `;

            // Render Static Archives
            const archiveGrid = container.querySelector('#archive-grid');
            archiveSites.forEach(s => {
                const btn = document.createElement('button');
                btn.className = 'cam-grid-btn archive-link';
                btn.dataset.url = s.url;
                
                const img = document.createElement('img');
                img.src = getFaviconUrl(s.domain);
                img.onerror = () => { img.style.display = 'none'; };
                
                const txt = document.createTextNode(` ${s.name}`);
                
                btn.appendChild(img);
                btn.appendChild(txt);
                archiveGrid.appendChild(btn);
            });

            this.renderSettings(container.querySelector('#settings-content'));

            document.body.appendChild(backdrop);
            document.body.appendChild(container);

            this.elements = {
                input: container.querySelector('#cam-user-input'),
                tabs: container.querySelectorAll('.cam-nav-item'),
                contents: container.querySelectorAll('.cam-tab-content')
            };

            // Username extraction (Safe)
            try {
                const path = window.location.pathname.split('/').filter(p => p);
                let initialUser = '';
                const cbIgnore = ['tags', 'auth', 'search', 'couple-cams', 'female-cams', 'trans-cams', 'male-cams'];

                if (window.location.hostname.includes('chaturbate') && path.length === 1 && !cbIgnore.includes(path[0])) {
                    initialUser = path[0];
                }
                else if (!window.location.hostname.includes('chaturbate') && path.length >= 1) {
                    initialUser = path[path.length-1];
                }
                
                if(initialUser && Utils.isValidUsername(initialUser)) {
                    this.elements.input.value = initialUser;
                    this.onUserChange(initialUser);
                }
            } catch(e) { console.error("Extraction error", e); }

            this.bindEvents(backdrop, container);
        },

        detectSite: function() {
            return window.location.hostname.includes('chaturbate') ? 'chaturbate' : 'stripchat';
        },

        bindEvents: function(backdrop, container) {
            const close = () => this.close();
            backdrop.addEventListener('click', close);
            container.querySelector('.cam-close').addEventListener('click', close);

            this.elements.tabs.forEach(t => {
                t.addEventListener('click', () => {
                    this.elements.tabs.forEach(x => x.classList.remove('active'));
                    t.classList.add('active');
                    this.elements.contents.forEach(c => c.style.display = 'none');
                    container.querySelector(`#tab-${t.dataset.tab}`).style.display = 'block';
                    if (t.dataset.tab === 'saved') this.renderSaved();
                });
            });

            let debounce;
            this.elements.input.addEventListener('input', () => {
                clearTimeout(debounce);
                debounce = setTimeout(() => {
                    const val = this.elements.input.value.trim();
                    if(Utils.isValidUsername(val)) {
                        this.onUserChange(val);
                    }
                }, 800);
            });

            container.querySelectorAll('.archive-link').forEach(b => {
                b.addEventListener('click', () => {
                    if(b.classList.contains('unavailable')) return;
                    const u = this.elements.input.value.trim();
                    if(u && Utils.isValidUsername(u)) {
                        Utils.openSafe(b.dataset.url.replace('{username}', u));
                    }
                });
            });

            container.querySelectorAll('.main-site').forEach(b => {
                b.addEventListener('click', () => {
                    const u = this.elements.input.value.trim();
                    if(u) Utils.openSafe(mainSites[b.dataset.site].replace('{username}', u));
                });
            });

            container.querySelectorAll('.extra-tool').forEach(b => {
                b.addEventListener('click', () => {
                    const u = this.elements.input.value.trim();
                    if (!u) return;
                    
                    const currentSite = this.detectSite(); 
                    let url = '';
            
                    switch (b.dataset.type) {
                        case 'schedule': url = `https://www.cbhours.com/user/${u}.html`; break;
                        case 'stats': url = `https://statbate.com/search/1/${u}`; break;
                        case 'find':
                            const prefix = currentSite === 'stripchat' ? 'sc' : 'cb';
                            url = `https://camgirlfinder.net/models/${prefix}/${u}`;
                            break;
                    }
                    if (url) Utils.openSafe(url);
                });
            });

            container.querySelector('#cam-save-btn').addEventListener('click', () => {
                const u = this.elements.input.value.trim();
                if(!u) return;
                if(ProfileManager.add(u)) this.toast(`Saved ${u}`);
                else this.toast('Already saved');
            });
        },

        renderSettings: function(container) {
            container.innerHTML = `
                <div class="cam-section">
                    <div class="cam-section-head">Data Management</div>
                    <button class="cam-btn" id="btn-export">📤 Export JSON</button>
                    <button class="cam-btn" id="btn-import">📥 Import JSON</button>
                    <input type="file" id="file-import" style="display:none" accept=".json">
                </div>
                <div style="text-align:center; color:var(--cam-text-muted); font-size:12px; margin-top:30px;">
                    Cam ARNA v2.1.1
                </div>
            `;

            container.querySelector('#btn-export').onclick = () => {
                const data = JSON.stringify(ProfileManager.get());
                const blob = new Blob([data], {type: 'application/json'});
                const url = URL.createObjectURL(blob);
                const a = document.createElement('a');
                a.href = url; a.download = 'cam_rna_backup.json';
                a.click();
                URL.revokeObjectURL(url);
            };

            const fInput = container.querySelector('#file-import');
            container.querySelector('#btn-import').onclick = () => fInput.click();
            fInput.onchange = (e) => {
                if(!e.target.files[0]) return;
                const reader = new FileReader();
                reader.onload = (ev) => {
                    try {
                        const list = JSON.parse(ev.target.result);
                        if(Array.isArray(list) && list.every(item => typeof item === 'string' && Utils.isValidUsername(item))) {
                            Storage.set('cam_profiles', list);
                            this.toast(`Imported ${list.length} profiles`);
                            this.renderSaved();
                        } else { throw new Error(); }
                    } catch(err) { this.toast('Error: Invalid JSON file'); }
                };
                reader.readAsText(e.target.files[0]);
            };
        },

        close: function() {
            const bd = document.querySelector('.cam-backdrop');
            const ct = document.querySelector('.cam-container');
            if(bd) bd.remove();
            if(ct) ct.remove();
            this.isOpen = false;
        },

        onUserChange: async function(username) {
            if(!username) return;

            document.querySelectorAll('.archive-link').forEach(b => b.classList.remove('unavailable', 'checking'));

            const status = document.getElementById('archive-status');
            if(status) status.innerText = "Checking...";

            let avail = 0;
            const btns = Array.from(document.querySelectorAll('.archive-link'));
            
            const promises = btns.map(async b => {
                if(!b.isConnected) return;
                b.classList.add('checking');
                const ok = await PageChecker.checkPage(b.dataset.url.replace('{username}', username));
                if(b.isConnected) { 
                    b.classList.remove('checking');
                    if(!ok) b.classList.add('unavailable');
                    else avail++;
                }
            });

            await Promise.allSettled(promises);
            if(status && status.isConnected) status.innerText = `${avail} found`;
        },

        renderSaved: function() {
            try {
                const list = ProfileManager.get();
                const el = document.getElementById('saved-list');
                const empty = document.getElementById('saved-empty');
                if(!el) return;

                if(list.length === 0) {
                    el.style.display = 'none';
                    empty.style.display = 'block';
                } else {
                    el.style.display = 'block';
                    empty.style.display = 'none';
                    el.innerHTML = '';
                    
                    list.forEach(u => {
                        const row = document.createElement('div');
                        row.style.cssText = "padding:12px 16px; border-bottom:1px solid var(--cam-border); display:flex; justify-content:space-between; align-items:center;";
                        
                        const nameSpan = document.createElement('span');
                        nameSpan.style.fontWeight = "600";
                        nameSpan.textContent = u;
                        
                        const actions = document.createElement('div');
                        actions.style.cssText = "display:flex; gap:8px;";
                        
                        const btnCB = document.createElement('button');
                        btnCB.textContent = '🧡';
                        btnCB.style.cssText = "border:none; background:none; cursor:pointer;";
                        btnCB.onclick = () => Utils.openSafe(`https://chaturbate.com/${u}/`);
                        
                        const btnSC = document.createElement('button');
                        btnSC.textContent = '💜';
                        btnSC.style.cssText = "border:none; background:none; cursor:pointer;";
                        btnSC.onclick = () => Utils.openSafe(`https://stripchat.com/${u}`);
                        
                        const btnDel = document.createElement('button');
                        btnDel.textContent = '🗑️';
                        btnDel.style.cssText = "border:none; background:none; cursor:pointer;";
                        btnDel.onclick = () => {
                            ProfileManager.remove(u);
                            this.renderSaved();
                        };
                        
                        actions.append(btnCB, btnSC, btnDel);
                        row.append(nameSpan, actions);
                        el.appendChild(row);
                    });
                }
            } catch(e) { console.error("Render error", e); }
        },

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

    function init() {
        injectStyles();
        const fab = document.createElement('button');
        fab.id = 'cam-fab';
        fab.textContent = '⚡';
        fab.onclick = () => UI.createMenu();
        document.body.appendChild(fab);
    }

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

})();