CW.tv Ultimate Toolbox 2025

Adds filtering tools, private video checking, and infinite scroll to camwhores.tv

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

ستحتاج إلى تثبيت إضافة مثل Stylus لتثبيت هذا النمط.

ستحتاج إلى تثبيت إضافة لإدارة أنماط المستخدم لتتمكن من تثبيت هذا النمط.

ستحتاج إلى تثبيت إضافة لإدارة أنماط المستخدم لتثبيت هذا النمط.

ستحتاج إلى تثبيت إضافة لإدارة أنماط المستخدم لتثبيت هذا النمط.

(لدي بالفعل مثبت أنماط للمستخدم، دعني أقم بتثبيته!)

// ==UserScript==
// @name         CW.tv Ultimate Toolbox 2025
// @namespace    http://tampermonkey.net/
// @version      1.4
// @description  Adds filtering tools, private video checking, and infinite scroll to camwhores.tv
// @author       cakehorn
// @match        https://www.camwhores.tv/*
// @grant        GM_xmlhttpRequest
// @connect      camwhores.tv
// ==/UserScript==

(function() {
    'use strict';
    
    // Configuration
    const config = {
        scrollThreshold: 500, // Distance from bottom (in px) to trigger loading more content
        loadingDelay: 500,    // Delay in ms to prevent excessive requests
        filterDelay: 300,     // Delay before applying filter (for typing)
        checkBatchSize: 5,    // Number of videos to check at once
        checkDelay: 800,      // Delay between batch checks (ms)
        markPrivateVideos: true, // Add visual indicators to private videos
    };
    
    // Variables
    let isLoading = false;
    let isCheckingFriends = false;
    let nextPageUrl = null;
    let filterTimer = null;
    let currentKeywordFilter = '';
    let currentMinDuration = 0; // in seconds
    
    // Helper function to get the next page URL
    function getNextPageUrl() {
        const paginationLinks = document.querySelectorAll('.pagination a');
        for (let i = 0; i < paginationLinks.length; i++) {
            if (paginationLinks[i].textContent.includes('Next')) {
                return paginationLinks[i].href;
            }
        }
        return null;
    }
    
    // Helper function to parse duration string (e.g., "5:30" to seconds)
    function parseDuration(durationStr) {
        if (!durationStr) return 0;
        
        // Handle different duration formats
        const parts = durationStr.trim().split(':');
        let seconds = 0;
        
        if (parts.length === 3) { // hours:minutes:seconds
            seconds = parseInt(parts[0]) * 3600 + parseInt(parts[1]) * 60 + parseInt(parts[2]);
        } else if (parts.length === 2) { // minutes:seconds
            seconds = parseInt(parts[0]) * 60 + parseInt(parts[1]);
        } else if (parts.length === 1) { // seconds only
            seconds = parseInt(parts[0]);
        }
        
        return isNaN(seconds) ? 0 : seconds;
    }
    
    // Add tools panel with all features
    function addToolsPanel() {
        const toolsPanel = document.createElement('div');
        toolsPanel.style.cssText = `
            position: fixed;
            bottom: 20px;
            left: 20px;
            background-color: #333;
            color: white;
            padding: 15px;
            border-radius: 5px;
            z-index: 9999;
            display: flex;
            flex-direction: column;
            box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2);
            width: 250px;
            max-height: 90vh;
            overflow-y: auto;
        `;
        
        // Add title
        const panelTitle = document.createElement('div');
        panelTitle.textContent = 'Video Tools';
        panelTitle.style.cssText = `
            font-weight: bold;
            font-size: 16px;
            margin-bottom: 10px;
            text-align: center;
            border-bottom: 1px solid #555;
            padding-bottom: 5px;
        `;
        toolsPanel.appendChild(panelTitle);
        
        // 1. Keyword filter
        const keywordFilterContainer = document.createElement('div');
        keywordFilterContainer.style.cssText = `margin-bottom: 10px;`;
        
        const keywordLabel = document.createElement('label');
        keywordLabel.textContent = 'Filter by keyword:';
        keywordLabel.style.cssText = `display: block; margin-bottom: 5px;`;
        
        const keywordInput = document.createElement('input');
        keywordInput.type = 'text';
        keywordInput.placeholder = 'Enter keyword to filter...';
        keywordInput.style.cssText = `
            width: 100%;
            padding: 5px;
            border-radius: 4px;
            border: 1px solid #555;
            background-color: #222;
            color: white;
            box-sizing: border-box;
        `;
        
        keywordFilterContainer.appendChild(keywordLabel);
        keywordFilterContainer.appendChild(keywordInput);
        toolsPanel.appendChild(keywordFilterContainer);
        
        // 2. Minimum duration filter
        const durationFilterContainer = document.createElement('div');
        durationFilterContainer.style.cssText = `margin-bottom: 10px;`;
        
        const durationLabel = document.createElement('label');
        durationLabel.textContent = 'Min duration (mm:ss):';
        durationLabel.style.cssText = `display: block; margin-bottom: 5px;`;
        
        const durationInput = document.createElement('input');
        durationInput.type = 'text';
        durationInput.placeholder = 'e.g., 5:00 for 5 minutes';
        durationInput.style.cssText = `
            width: 100%;
            padding: 5px;
            border-radius: 4px;
            border: 1px solid #555;
            background-color: #222;
            color: white;
            box-sizing: border-box;
        `;
        
        durationFilterContainer.appendChild(durationLabel);
        durationFilterContainer.appendChild(durationInput);
        toolsPanel.appendChild(durationFilterContainer);
        
        // Apply filters button
        const applyButton = document.createElement('button');
        applyButton.textContent = 'Apply Filters';
        applyButton.style.cssText = `
            padding: 5px 10px;
            background-color: #4CAF50;
            color: white;
            border: none;
            border-radius: 4px;
            cursor: pointer;
            font-weight: bold;
            margin-bottom: 15px;
        `;
        toolsPanel.appendChild(applyButton);
        
        // Separator
        const separator1 = document.createElement('div');
        separator1.style.cssText = `
            border-top: 1px solid #555;
            margin: 5px 0 15px 0;
        `;
        toolsPanel.appendChild(separator1);
        
        // 3. Friend Check Features
        const friendCheckTitle = document.createElement('div');
        friendCheckTitle.textContent = 'Private Video Tools';
        friendCheckTitle.style.cssText = `
            font-weight: bold;
            margin-bottom: 10px;
        `;
        toolsPanel.appendChild(friendCheckTitle);
        
        // Check Friend button
        const checkFriendButton = document.createElement('button');
        checkFriendButton.textContent = 'CHECK FRIEND STATUS';
        checkFriendButton.style.cssText = `
            padding: 8px 10px;
            background-color: #FF9800;
            color: white;
            border: none;
            border-radius: 4px;
            cursor: pointer;
            font-weight: bold;
            margin-bottom: 10px;
            width: 100%;
        `;
        toolsPanel.appendChild(checkFriendButton);
        
        // Erase Inaccessible button
        const eraseButton = document.createElement('button');
        eraseButton.textContent = 'ERASE INACCESSIBLE VIDEOS';
        eraseButton.style.cssText = `
            padding: 8px 10px;
            background-color: #F44336;
            color: white;
            border: none;
            border-radius: 4px;
            cursor: pointer;
            font-weight: bold;
            margin-bottom: 15px;
            width: 100%;
        `;
        toolsPanel.appendChild(eraseButton);
        
        // Status display for friend check
        const checkStatusDisplay = document.createElement('div');
        checkStatusDisplay.id = 'friend-check-status';
        checkStatusDisplay.style.cssText = `
            font-size: 12px;
            margin-bottom: 15px;
            padding: 5px;
            background-color: #222;
            border-radius: 3px;
            display: none;
        `;
        toolsPanel.appendChild(checkStatusDisplay);
        
        // Separator
        const separator2 = document.createElement('div');
        separator2.style.cssText = `
            border-top: 1px solid #555;
            margin: 5px 0 15px 0;
        `;
        toolsPanel.appendChild(separator2);
        
        // 4. Navigation Shortcuts
        const navTitle = document.createElement('div');
        navTitle.textContent = 'Quick Navigation';
        navTitle.style.cssText = `
            font-weight: bold;
            margin-bottom: 10px;
        `;
        toolsPanel.appendChild(navTitle);
        
        // Profile button
        const profileButton = document.createElement('button');
        profileButton.textContent = 'MY PROFILE';
        profileButton.style.cssText = `
            padding: 8px 10px;
            background-color: #2196F3;
            color: white;
            border: none;
            border-radius: 4px;
            cursor: pointer;
            font-weight: bold;
            margin-bottom: 10px;
            width: 100%;
        `;
        toolsPanel.appendChild(profileButton);
        
        // Favorites button
        const favoritesButton = document.createElement('button');
        favoritesButton.textContent = 'MY FAVORITES';
        favoritesButton.style.cssText = `
            padding: 8px 10px;
            background-color: #9C27B0;
            color: white;
            border: none;
            border-radius: 4px;
            cursor: pointer;
            font-weight: bold;
            margin-bottom: 15px;
            width: 100%;
        `;
        toolsPanel.appendChild(favoritesButton);
        
        // Add the panel to the page
        document.body.appendChild(toolsPanel);
        
        // Event listeners for filtering
        keywordInput.addEventListener('input', function() {
            clearTimeout(filterTimer);
            filterTimer = setTimeout(() => {
                currentKeywordFilter = this.value.toLowerCase().trim();
                applyFilters();
            }, config.filterDelay);
        });
        
        durationInput.addEventListener('input', function() {
            clearTimeout(filterTimer);
            filterTimer = setTimeout(() => {
                // Parse the input duration
                const durationStr = this.value.trim();
                if (durationStr) {
                    currentMinDuration = parseDuration(durationStr);
                } else {
                    currentMinDuration = 0;
                }
                applyFilters();
            }, config.filterDelay);
        });
        
        applyButton.addEventListener('click', function() {
            currentKeywordFilter = keywordInput.value.toLowerCase().trim();
            const durationStr = durationInput.value.trim();
            if (durationStr) {
                currentMinDuration = parseDuration(durationStr);
            } else {
                currentMinDuration = 0;
            }
            applyFilters();
        });
        
        // Friend Check functionality
        checkFriendButton.addEventListener('click', function() {
            if (isCheckingFriends) return;
            
            // Get all video items
            const videoItems = Array.from(document.querySelectorAll('.thumb, .item'));
            if (videoItems.length === 0) {
                alert('No videos found on this page.');
                return;
            }
            
            // Look for private video indicators
            const privateVideos = videoItems.filter(item => {
                // Look for any private video indicators
                return item.querySelector('.private, .lock, [class*="private"], [class*="lock"]') !== null ||
                       (item.textContent && (item.textContent.includes('Private') || item.textContent.includes('Friends only')));
            });
            
            if (privateVideos.length === 0) {
                alert('No private videos found on this page.');
                return;
            }
            
            isCheckingFriends = true;
            checkStatusDisplay.style.display = 'block';
            checkStatusDisplay.innerHTML = `Found ${privateVideos.length} private videos. Starting check...`;
            checkStatusDisplay.style.color = '#FFC107';
            
            // Process videos in batches to avoid overloading
            checkPrivateVideos(privateVideos, 0, checkStatusDisplay);
        });
        
        // Erase inaccessible videos
        eraseButton.addEventListener('click', function() {
            const inaccessibleVideos = document.querySelectorAll('.private-video-inaccessible');
            if (inaccessibleVideos.length === 0) {
                alert('No inaccessible videos found. Run CHECK FRIEND STATUS first.');
                return;
            }
            
            // Remove all inaccessible videos
            inaccessibleVideos.forEach(item => {
                item.style.display = 'none';
            });
            
            alert(`Removed ${inaccessibleVideos.length} inaccessible private videos from view.`);
        });
        
        // Navigation button event listeners
        profileButton.addEventListener('click', function() {
            // Navigate to profile page - adjust URL as needed
            window.location.href = 'https://www.camwhores.tv/my/';
        });
        
        favoritesButton.addEventListener('click', function() {
            // Navigate to favorites page - adjust URL as needed
            window.location.href = 'https://www.camwhores.tv/my/favourites/videos/';
        });
        
        return toolsPanel;
    }
    
    // Check private videos for accessibility - Fixed function
    function checkPrivateVideos(videos, startIndex, statusDisplay) {
        if (startIndex >= videos.length) {
            isCheckingFriends = false;
            statusDisplay.innerHTML = `✅ Completed checking ${videos.length} private videos.`;
            statusDisplay.style.color = '#4CAF50';
            
            // Count results
            const accessible = document.querySelectorAll('.private-video-accessible').length;
            const inaccessible = document.querySelectorAll('.private-video-inaccessible').length;
            
            statusDisplay.innerHTML += `<br>✓ Accessible: ${accessible}<br>✗ Inaccessible: ${inaccessible}`;
            return;
        }
        
        // Process a batch of videos
        const endIndex = Math.min(startIndex + config.checkBatchSize, videos.length);
        statusDisplay.innerHTML = `Checking private videos ${startIndex+1}-${endIndex} of ${videos.length}...`;
        
        // Process each video in the current batch
        for (let i = startIndex; i < endIndex; i++) {
            const videoItem = videos[i];
            const link = videoItem.querySelector('a[href*="/videos/"]');
            
            if (!link) continue;
            
            const videoUrl = link.href;
            
            // Check if this video has already been processed
            if (videoItem.classList.contains('private-video-checked')) {
                continue;
            }
            
            // Mark as being processed
            videoItem.classList.add('private-video-checked');
            
            // Create a fetch request to check accessibility
            fetch(videoUrl)
                .then(response => response.text())
                .then(html => {
                    // FIX: More robust check for inaccessible videos
                    // Check if the response indicates an inaccessible video first
                    const isInaccessible = 
                        html.includes('need to be friends') || 
                        html.includes('private video') ||
                        html.includes('friends only') ||
                        html.includes('You are not friends') ||
                        // Check if there's no video player
                        (!html.includes('video-player') && 
                         !html.includes('video_player') && 
                         !html.includes('player-holder'));
                    
                    // Mark the video accordingly
                    if (!isInaccessible) {
                        videoItem.classList.add('private-video-accessible');
                        
                        // Highlight title in blue and green
                        const titleElement = videoItem.querySelector('.title a, a.title');
                        if (titleElement) {
                            titleElement.style.color = '#00C853';
                            titleElement.style.textShadow = '0 0 2px #2196F3';
                            titleElement.style.fontWeight = 'bold';
                        }
                    } else {
                        videoItem.classList.add('private-video-inaccessible');
                        
                        // Add red X through title
                        const titleElement = videoItem.querySelector('.title a, a.title');
                        if (titleElement) {
                            titleElement.style.color = '#F44336';
                            titleElement.style.textDecoration = 'line-through';
                            
                            // Add X icon
                            const xIcon = document.createElement('span');
                            xIcon.textContent = '❌ ';
                            xIcon.style.color = '#F44336';
                            titleElement.parentNode.insertBefore(xIcon, titleElement);
                        }
                        
                        // Add semi-transparent overlay
                        const overlay = document.createElement('div');
                        overlay.style.cssText = `
                            position: absolute;
                            top: 0;
                            left: 0;
                            width: 100%;
                            height: 100%;
                            background-color: rgba(0, 0, 0, 0.6);
                            display: flex;
                            justify-content: center;
                            align-items: center;
                            z-index: 5;
                        `;
                        
                        const notAccessible = document.createElement('div');
                        notAccessible.textContent = 'NOT ACCESSIBLE';
                        notAccessible.style.cssText = `
                            color: #F44336;
                            font-weight: bold;
                            background-color: rgba(0, 0, 0, 0.7);
                            padding: 5px 10px;
                            border-radius: 3px;
                            transform: rotate(-15deg);
                        `;
                        
                        overlay.appendChild(notAccessible);
                        
                        // Make sure the video container has position relative
                        const imgContainer = videoItem.querySelector('.thumb-container, .img');
                        if (imgContainer) {
                            imgContainer.style.position = 'relative';
                            imgContainer.appendChild(overlay);
                        }
                    }
                })
                .catch(error => {
                    console.error('Error checking video:', error);
                    // Mark as error but still continue
                    videoItem.classList.add('private-video-error');
                });
        }
        
        // Schedule the next batch
        setTimeout(() => {
            checkPrivateVideos(videos, endIndex, statusDisplay);
        }, config.checkDelay);
    }
    
    // Apply filters to video items
    function applyFilters() {
        const videoItems = document.querySelectorAll('.thumb, .item');
        let hiddenCount = 0;
        
        videoItems.forEach(item => {
            let shouldShow = true;
            
            // Get the title for keyword filtering
            const titleElement = item.querySelector('.title a, a.title');
            const title = titleElement ? titleElement.textContent.toLowerCase() : '';
            
            // Check keyword filter
            if (currentKeywordFilter && !title.includes(currentKeywordFilter)) {
                shouldShow = false;
            }
            
            // Check duration filter if still visible
            if (shouldShow && currentMinDuration > 0) {
                const durationElement = item.querySelector('.duration');
                if (durationElement) {
                    const videoDuration = parseDuration(durationElement.textContent);
                    if (videoDuration < currentMinDuration) {
                        shouldShow = false;
                    }
                }
            }
            
            // Apply visibility
            if (shouldShow) {
                item.style.display = '';
            } else {
                item.style.display = 'none';
                hiddenCount++;
            }
        });
        
        // Display filter results
        const existingStatus = document.getElementById('filter-status');
        if (existingStatus) {
            existingStatus.remove();
        }
        
        if (currentKeywordFilter || currentMinDuration > 0) {
            const statusDiv = document.createElement('div');
            statusDiv.id = 'filter-status';
            statusDiv.style.cssText = `
                position: fixed;
                top: 20px;
                right: 20px;
                background-color: rgba(33, 33, 33, 0.8);
                color: white;
                padding: 10px;
                border-radius: 5px;
                z-index: 9999;
                font-size: 14px;
            `;
            
            const totalVideos = videoItems.length;
            const visibleVideos = totalVideos - hiddenCount;
            
            statusDiv.innerHTML = `
                <b>Filter active:</b> Showing ${visibleVideos} of ${totalVideos} videos<br>
                ${currentKeywordFilter ? `<b>Keyword:</b> "${currentKeywordFilter}"<br>` : ''}
                ${currentMinDuration > 0 ? `<b>Min duration:</b> ${Math.floor(currentMinDuration/60)}:${(currentMinDuration%60).toString().padStart(2, '0')}` : ''}
            `;
            
            document.body.appendChild(statusDiv);
            
            // Auto-hide after 5 seconds
            setTimeout(() => {
                if (statusDiv.parentNode) {
                    statusDiv.style.opacity = '0';
                    statusDiv.style.transition = 'opacity 1s';
                    setTimeout(() => statusDiv.remove(), 1000);
                }
            }, 5000);
        }
    }
    
    // Check if the current page has videos (to apply infinite scroll)
    function isVideoPage() {
        // Check if the page contains video thumbnails or a pagination element
        return document.querySelectorAll('.thumb, .pagination').length > 0;
    }
    
    // Load more content when scrolling
    function loadMoreContent() {
        if (isLoading || !nextPageUrl) return;
        
        isLoading = true;
        
        // Create a loading indicator
        const loadingIndicator = document.createElement('div');
        loadingIndicator.style.cssText = `
            text-align: center;
            padding: 20px;
            font-size: 16px;
            color: #999;
        `;
        loadingIndicator.innerHTML = 'Loading more videos...';
        
        // Find the container to append to
        const contentContainer = document.querySelector('.thumbs, .list-videos');
        if (contentContainer) {
            contentContainer.appendChild(loadingIndicator);
            
            // Fetch the next page
            fetch(nextPageUrl)
                .then(response => response.text())
                .then(html => {
                    const parser = new DOMParser();
                    const doc = parser.parseFromString(html, 'text/html');
                    
                    // Extract the video elements from the next page
                    const newItems = doc.querySelectorAll('.thumb, .item');
                    
                    // Append the new items to the current page
                    newItems.forEach(item => {
                        const clonedItem = item.cloneNode(true);
                        contentContainer.appendChild(clonedItem);
                        
                        // Apply current filters to new items
                        if ((currentKeywordFilter && currentKeywordFilter !== '') || currentMinDuration > 0) {
                            let shouldShow = true;
                            
                            // Check keyword filter
                            if (currentKeywordFilter && currentKeywordFilter !== '') {
                                const titleElement = clonedItem.querySelector('.title a, a.title');
                                const title = titleElement ? titleElement.textContent.toLowerCase() : '';
                                if (!title.includes(currentKeywordFilter)) {
                                    shouldShow = false;
                                }
                            }
                            
                            // Check duration filter
                            if (shouldShow && currentMinDuration > 0) {
                                const durationElement = clonedItem.querySelector('.duration');
                                if (durationElement) {
                                    const videoDuration = parseDuration(durationElement.textContent);
                                    if (videoDuration < currentMinDuration) {
                                        shouldShow = false;
                                    }
                                }
                            }
                            
                            if (!shouldShow) {
                                clonedItem.style.display = 'none';
                            }
                        }
                    });
                    
                    // Update the next page URL
                    nextPageUrl = getNextPageUrl();
                    
                    // Remove the loading indicator
                    loadingIndicator.remove();
                    
                    // Set a small delay before allowing more loads
                    setTimeout(() => {
                        isLoading = false;
                    }, config.loadingDelay);
                })
                .catch(error => {
                    console.error('Error loading more content:', error);
                    loadingIndicator.innerHTML = 'Error loading more videos. <a href="' + nextPageUrl + '">Click here to try again</a>.';
                    
                    setTimeout(() => {
                        isLoading = false;
                    }, config.loadingDelay);
                });
        }
    }
    
    // Scroll event listener for infinite scroll
    function setupInfiniteScroll() {
        window.addEventListener('scroll', function() {
            const scrollHeight = Math.max(
                document.body.scrollHeight,
                document.documentElement.scrollHeight
            );
            const scrollTop = window.scrollY;
            const clientHeight = document.documentElement.clientHeight;
            
            // Check if user has scrolled close to the bottom
            if (scrollHeight - scrollTop - clientHeight < config.scrollThreshold) {
                loadMoreContent();
            }
        });
    }
    
    // Initialize the script
    function init() {
        // Add CSS for private video styling
        const styleElement = document.createElement('style');
        styleElement.textContent = `
            .private-video-accessible .title a {
                color: #00C853 !important;
                text-shadow: 0 0 2px #2196F3;
                font-weight: bold;
            }
            
            .private-video-inaccessible .title a {
                color: #F44336 !important;
                text-decoration: line-through !important;
            }
            
            .private-video-inaccessible {
                opacity: 0.8;
            }
        `;
        document.head.appendChild(styleElement);
        
        // Add the tools panel
        addToolsPanel();
        
        // Setup infinite scroll only on video pages
        if (isVideoPage()) {
            nextPageUrl = getNextPageUrl();
            setupInfiniteScroll();
        }
    }
    
    // Run the initialization when the page is fully loaded
    window.addEventListener('load', init);
})();