您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Animate multiple Chaturbate rooms simultaneously with draggable/minimizable panel
// ==UserScript== // @name Chaturbate Multi-Room Animator (Draggable + Minimizable) // @namespace http://tampermonkey.net/ // @version 2.3 // @description Animate multiple Chaturbate rooms simultaneously with draggable/minimizable panel // @author You // @match https://chaturbate.com/* // @grant none // @license MIT // ==/UserScript== (function() { 'use strict'; // Configuration with localStorage persistence let config = { simultaneousRooms: 5, animationSpeed: 2000, enabled: false, panelPosition: { x: null, y: null }, isMinimized: false }; // Drag state let isDragging = false; let dragOffset = { x: 0, y: 0 }; // Simple save function that definitely gets called function simpleSave() { try { localStorage.setItem('multiRoom_rooms', config.simultaneousRooms); localStorage.setItem('multiRoom_speed', config.animationSpeed); if (config.panelPosition.x !== null && config.panelPosition.y !== null) { localStorage.setItem('multiRoom_panel_x', config.panelPosition.x); localStorage.setItem('multiRoom_panel_y', config.panelPosition.y); } localStorage.setItem('multiRoom_minimized', config.isMinimized); } catch (e) { console.error('❌ Error saving settings:', e); } } // Simple load function function simpleLoad() { try { const rooms = localStorage.getItem('multiRoom_rooms'); const speed = localStorage.getItem('multiRoom_speed'); const panelX = localStorage.getItem('multiRoom_panel_x'); const panelY = localStorage.getItem('multiRoom_panel_y'); const minimized = localStorage.getItem('multiRoom_minimized'); if (rooms) config.simultaneousRooms = parseInt(rooms); if (speed) { const loadedSpeed = parseInt(speed); // Ensure speed is within new bounds (10-500ms) config.animationSpeed = Math.max(10, Math.min(500, loadedSpeed)); } if (panelX && panelY) { config.panelPosition.x = parseInt(panelX); config.panelPosition.y = parseInt(panelY); } if (minimized !== null) { config.isMinimized = minimized === 'true'; } console.log('📋 Settings loaded:', {rooms: config.simultaneousRooms, speed: config.animationSpeed}); } catch (e) { console.error('❌ Error loading settings:', e); } } let animationInterval; let roomElements = []; let controlPanel; let isPaused = false; let isCompletelyDisabled = false; let mutationObserver = null; // Create control panel function createControlPanel() { // Remove existing panel if it exists const existingPanel = document.getElementById('multi-room-animator-panel'); if (existingPanel) { existingPanel.remove(); } controlPanel = document.createElement('div'); controlPanel.id = 'multi-room-animator-panel'; // Determine initial position let initialX = config.panelPosition.x !== null ? config.panelPosition.x : (window.innerWidth - 250); let initialY = config.panelPosition.y !== null ? config.panelPosition.y : 10; controlPanel.style.cssText = ` position: fixed; left: ${initialX}px; top: ${initialY}px; background: #1a1a1a; border: 2px solid #ff6b35; border-radius: 7px; padding: 0; z-index: 9999; font-family: Arial, sans-serif; font-size: 13px; color: white; min-width: 224px; box-shadow: 0 3px 12px rgba(0,0,0,0.5); transform: scale(0.8); transform-origin: top left; user-select: none; `; // Create draggable title bar const titleBar = document.createElement('div'); titleBar.id = 'panel-title-bar'; titleBar.style.cssText = ` padding: 8px 12px; cursor: move; background: linear-gradient(135deg, #ff6b35, #ff8555); border-radius: 5px 5px 0 0; display: flex; justify-content: space-between; align-items: center; `; const titleContainer = document.createElement('div'); titleContainer.style.cssText = 'display: flex; align-items: center; gap: 8px;'; const title = document.createElement('h3'); title.style.cssText = 'margin: 0; color: white; font-size: 15px; pointer-events: none;'; title.textContent = 'Multi-Room Animator'; const dragIcon = document.createElement('span'); dragIcon.style.cssText = 'color: rgba(255,255,255,0.7); pointer-events: none; font-size: 18px;'; dragIcon.textContent = '⋮⋮'; titleContainer.appendChild(title); titleContainer.appendChild(dragIcon); // Add minimize button const minimizeBtn = document.createElement('button'); minimizeBtn.id = 'minimize-btn'; minimizeBtn.style.cssText = ` background: rgba(255,255,255,0.2); color: white; border: 1px solid rgba(255,255,255,0.3); padding: 2px 8px; border-radius: 3px; cursor: pointer; font-size: 14px; font-weight: bold; transition: background 0.2s; min-width: 24px; `; minimizeBtn.textContent = config.isMinimized ? '+' : '−'; minimizeBtn.title = config.isMinimized ? 'Restore' : 'Minimize'; titleBar.appendChild(titleContainer); titleBar.appendChild(minimizeBtn); // Create content container const contentContainer = document.createElement('div'); contentContainer.id = 'panel-content'; contentContainer.style.cssText = 'padding: 12px;'; if (config.isMinimized) { contentContainer.style.display = 'none'; } // Create elements step by step const roomCountDiv = document.createElement('div'); roomCountDiv.style.marginBottom = '9px'; const roomCountLabel = document.createElement('label'); roomCountLabel.style.cssText = 'display: block; margin-bottom: 4px; font-size: 12px;'; roomCountLabel.textContent = 'Simultaneous rooms to animate:'; const roomCountInput = document.createElement('input'); roomCountInput.type = 'number'; roomCountInput.id = 'simultaneous-room-count'; roomCountInput.value = config.simultaneousRooms; roomCountInput.min = '1'; roomCountInput.max = '50'; roomCountInput.style.cssText = 'width: 48px; padding: 4px; background: #333; color: white; border: 1px solid #555; font-size: 12px;'; const speedDiv = document.createElement('div'); speedDiv.style.marginBottom = '9px'; const speedLabel = document.createElement('label'); speedLabel.style.cssText = 'display: block; margin-bottom: 4px; font-size: 12px;'; speedLabel.innerHTML = `Refresh interval (ms): <span id="speed-display">${config.animationSpeed}</span>`; const speedInput = document.createElement('input'); speedInput.type = 'number'; speedInput.id = 'animation-speed'; speedInput.value = config.animationSpeed; speedInput.min = '10'; speedInput.max = '500'; speedInput.step = '10'; speedInput.style.cssText = 'width: 64px; padding: 4px; background: #333; color: white; border: 1px solid #555; font-size: 12px;'; const buttonDiv = document.createElement('div'); const toggleBtn = document.createElement('button'); toggleBtn.id = 'toggle-animation'; toggleBtn.textContent = 'Start'; toggleBtn.style.cssText = ` background: #ff6b35; color: white; border: none; padding: 6px 12px; border-radius: 3px; cursor: pointer; margin-right: 4px; font-size: 12px; `; const disableBtn = document.createElement('button'); disableBtn.id = 'disable-script'; disableBtn.textContent = 'Disable'; disableBtn.style.cssText = ` background: #dc3545; color: white; border: none; padding: 6px 12px; border-radius: 3px; cursor: pointer; margin-left: 4px; font-size: 12px; `; const statusDiv = document.createElement('div'); statusDiv.style.cssText = 'margin-top: 9px; font-size: 11px; color: #ccc;'; statusDiv.innerHTML = ` Status: <span id="status">Stopped</span><br> Rooms found: <span id="room-count-display">0</span><br> Animating: <span id="animating-display">-</span> `; // Assemble the panel roomCountDiv.appendChild(roomCountLabel); roomCountDiv.appendChild(roomCountInput); speedDiv.appendChild(speedLabel); speedDiv.appendChild(speedInput); buttonDiv.appendChild(toggleBtn); buttonDiv.appendChild(disableBtn); contentContainer.appendChild(roomCountDiv); contentContainer.appendChild(speedDiv); contentContainer.appendChild(buttonDiv); contentContainer.appendChild(statusDiv); controlPanel.appendChild(titleBar); controlPanel.appendChild(contentContainer); document.body.appendChild(controlPanel); // Setup minimize functionality minimizeBtn.onclick = (e) => { e.stopPropagation(); config.isMinimized = !config.isMinimized; if (config.isMinimized) { contentContainer.style.display = 'none'; minimizeBtn.textContent = '+'; minimizeBtn.title = 'Restore'; } else { contentContainer.style.display = 'block'; minimizeBtn.textContent = '−'; minimizeBtn.title = 'Minimize'; } simpleSave(); }; // Setup drag functionality setupDragFunctionality(titleBar, minimizeBtn); // Setup event listeners immediately after creation setupEventListeners(); } // Setup drag functionality for the panel function setupDragFunctionality(titleBar, minimizeBtn) { titleBar.addEventListener('mousedown', startDrag); document.addEventListener('mousemove', drag); document.addEventListener('mouseup', stopDrag); function startDrag(e) { if (e.target === minimizeBtn) return; e.preventDefault(); isDragging = true; const rect = controlPanel.getBoundingClientRect(); dragOffset.x = e.clientX - rect.left; dragOffset.y = e.clientY - rect.top; controlPanel.style.opacity = '0.8'; } function drag(e) { if (!isDragging) return; e.preventDefault(); let newX = e.clientX - dragOffset.x; let newY = e.clientY - dragOffset.y; const scale = 0.8; const panelWidth = controlPanel.offsetWidth * scale; const panelHeight = controlPanel.offsetHeight * scale; newX = Math.max(0, Math.min(newX, window.innerWidth - panelWidth)); newY = Math.max(0, Math.min(newY, window.innerHeight - panelHeight)); controlPanel.style.left = newX + 'px'; controlPanel.style.top = newY + 'px'; config.panelPosition.x = newX; config.panelPosition.y = newY; } function stopDrag() { if (!isDragging) return; isDragging = false; controlPanel.style.opacity = '1'; simpleSave(); } } // Setup event listeners for control panel function setupEventListeners() { try { const toggleBtn = controlPanel.querySelector('#toggle-animation'); const disableBtn = controlPanel.querySelector('#disable-script'); const roomCountInput = controlPanel.querySelector('#simultaneous-room-count'); const speedInput = controlPanel.querySelector('#animation-speed'); if (toggleBtn) { toggleBtn.onclick = () => toggleAnimation(); } if (disableBtn) { disableBtn.onclick = () => { completelyDisableScript(); }; } if (speedInput) { speedInput.onchange = (e) => { const newSpeed = parseInt(e.target.value); config.animationSpeed = newSpeed; simpleSave(); updateSpeedDisplay(); if (config.enabled) { restartAnimation(); } }; speedInput.oninput = (e) => { const newSpeed = parseInt(e.target.value); if (!isNaN(newSpeed)) { config.animationSpeed = newSpeed; simpleSave(); updateSpeedDisplay(); if (config.enabled) { restartAnimation(); } } }; } if (roomCountInput) { roomCountInput.onchange = (e) => { const newCount = parseInt(e.target.value); config.simultaneousRooms = newCount; simpleSave(); updateAnimatingDisplay(); if (config.enabled) { restartAnimation(); } }; roomCountInput.oninput = (e) => { const newCount = parseInt(e.target.value); if (!isNaN(newCount)) { config.simultaneousRooms = newCount; simpleSave(); updateAnimatingDisplay(); if (config.enabled) { restartAnimation(); } } }; } } catch (error) { console.error('❌ Error in setupEventListeners:', error); } } // Update speed display function updateSpeedDisplay() { try { const speedDisplay = document.getElementById('speed-display'); const speedInput = document.getElementById('animation-speed'); if (speedDisplay) { speedDisplay.textContent = config.animationSpeed; } if (speedInput) { speedInput.value = config.animationSpeed; } } catch (error) { console.error('❌ Error updating speed display:', error); } } // Check if an element is meaningfully visible in the viewport function isElementVisible(element) { const rect = element.getBoundingClientRect(); const windowHeight = window.innerHeight || document.documentElement.clientHeight; const windowWidth = window.innerWidth || document.documentElement.clientWidth; // Calculate how much of the element is visible const visibleTop = Math.max(rect.top, 0); const visibleBottom = Math.min(rect.bottom, windowHeight); const visibleLeft = Math.max(rect.left, 0); const visibleRight = Math.min(rect.right, windowWidth); const visibleHeight = Math.max(0, visibleBottom - visibleTop); const visibleWidth = Math.max(0, visibleRight - visibleLeft); const visibleArea = visibleHeight * visibleWidth; const totalArea = rect.height * rect.width; const visibilityPercentage = totalArea > 0 ? (visibleArea / totalArea) : 0; // Element must be at least 50% visible and have substantial visible area return visibilityPercentage >= 0.5 && visibleHeight >= 100; } // Get visible rooms from all found rooms function getVisibleRooms() { if (roomElements.length === 0) return []; const visibleRooms = roomElements.filter(room => isElementVisible(room)); return visibleRooms; } // Find room elements on the page function findRoomElements() { const selectors = [ 'li[class*="room"]', '.roomCard', 'div[class*="room"]', '.room_list_room', '.room-card', '[data-room]' ]; const previousCount = roomElements.length; roomElements = []; for (let selector of selectors) { try { const elements = document.querySelectorAll(selector); if (elements.length > 0) { roomElements = Array.from(elements); break; } } catch (e) { console.log(`Selector "${selector}" failed:`, e); } } if (roomElements.length !== previousCount) { console.log(`✅ Found ${roomElements.length} rooms on page (was ${previousCount})`); } updateRoomCountDisplay(); return roomElements.length > 0; } // Update the room count display function updateRoomCountDisplay() { try { const display = document.getElementById('room-count-display'); if (display) { display.textContent = roomElements.length; } updateAnimatingDisplay(); } catch (error) { console.error('❌ Error updating room count display:', error); } } // Update the animating rooms display function updateAnimatingDisplay() { try { const animatingDisplay = document.getElementById('animating-display'); if (animatingDisplay) { if (config.enabled && roomElements.length > 0) { const visibleRooms = getVisibleRooms(); const actualCount = Math.min(config.simultaneousRooms, visibleRooms.length); animatingDisplay.textContent = `${actualCount} visible rooms`; } else { animatingDisplay.textContent = '-'; } } } catch (error) { console.error('❌ Error updating animating display:', error); } } // Completely disable the script and remove all traces function completelyDisableScript() { console.log('🛑 completelyDisableScript() function called'); try { // Stop animation isCompletelyDisabled = true; config.enabled = false; // Clear ALL intervals (in case there are multiple) if (animationInterval) { clearInterval(animationInterval); animationInterval = null; console.log('✅ Animation interval cleared'); } // Clear all timeouts and intervals that might be running const highestId = window.setTimeout(() => {}, 0); for (let i = 0; i < highestId; i++) { window.clearTimeout(i); window.clearInterval(i); } console.log('✅ All timeouts and intervals cleared'); // Stop mutation observer if (mutationObserver) { mutationObserver.disconnect(); mutationObserver = null; console.log('✅ Mutation observer disconnected'); } // Stop all video previews immediately console.log('🛑 Stopping all video previews...'); roomElements.forEach(room => { // Remove preview images const preview = room.querySelector('.direct-video-preview'); const border = room.querySelector('.animation-border'); if (preview) preview.remove(); if (border) border.remove(); // Force stop all videos const videos = room.querySelectorAll('video'); videos.forEach(video => { video.pause(); video.currentTime = 0; video.src = ''; video.load(); }); }); console.log('✅ All video previews stopped and cleaned'); // Remove the control panel if (controlPanel) { controlPanel.remove(); controlPanel = null; console.log('✅ Control panel removed'); } // Force garbage collection by nullifying everything roomElements = null; config = null; console.log('🎉 Script completely disabled. CPU usage should drop now.'); alert('Script fully disabled! CPU usage should drop. Refresh page to re-enable.'); } catch (error) { console.error('❌ Error in completelyDisableScript:', error); } } // Create direct video preview using direct URL function createDirectVideoPreview(roomElement, username) { try { // Remove any existing preview and border const existingPreview = roomElement.querySelector('.direct-video-preview'); const existingBorder = roomElement.querySelector('.animation-border'); if (existingPreview) { existingPreview.remove(); } if (existingBorder) { existingBorder.remove(); } // Create the direct thumbnail URL with cache busting const cacheBuster = Math.random(); const thumbnailUrl = `https://thumb.live.mmcdn.com/minifwap/${username}.jpg?${cacheBuster}`; // Create image element for the preview const previewImg = document.createElement('img'); previewImg.className = 'direct-video-preview'; // Create border overlay element const borderOverlay = document.createElement('div'); borderOverlay.className = 'animation-border'; borderOverlay.style.cssText = ` position: absolute !important; top: 0 !important; left: 0 !important; width: 100% !important; height: 100% !important; box-shadow: inset 0 0 0 2px rgba(40, 167, 69, 0.8) !important; z-index: 10000 !important; pointer-events: none !important; `; // Add to DOM first, then set src to ensure it's connected const thumbnailContainer = roomElement.querySelector('.room_thumbnail_container'); const existingImg = roomElement.querySelector('img'); if (thumbnailContainer) { thumbnailContainer.style.position = 'relative'; thumbnailContainer.appendChild(previewImg); thumbnailContainer.appendChild(borderOverlay); } else if (existingImg && existingImg.parentElement) { existingImg.parentElement.style.position = 'relative'; existingImg.parentElement.appendChild(previewImg); existingImg.parentElement.appendChild(borderOverlay); } else { roomElement.style.position = 'relative'; roomElement.appendChild(previewImg); roomElement.appendChild(borderOverlay); } // Set image styles previewImg.style.cssText = ` position: absolute !important; top: 0 !important; left: 0 !important; width: 100% !important; height: 100% !important; object-fit: cover !important; z-index: 9999 !important; pointer-events: none !important; `; // Add error handler previewImg.onerror = () => { previewImg.remove(); borderOverlay.remove(); }; // Set the src previewImg.src = thumbnailUrl; return previewImg; } catch (error) { return null; } } // Remove direct video preview function removeDirectVideoPreview(roomElement) { try { const existingPreview = roomElement.querySelector('.direct-video-preview'); const existingBorder = roomElement.querySelector('.animation-border'); if (existingPreview) { existingPreview.remove(); } if (existingBorder) { existingBorder.remove(); } } catch (error) { // Silently handle errors } } // Animate multiple rooms simultaneously - DIRECT URL METHOD function animateMultipleRooms() { if (roomElements.length === 0 || isCompletelyDisabled) return; // Get only the visible rooms const visibleRooms = getVisibleRooms(); if (visibleRooms.length === 0) { return; } const roomsToAnimate = Math.min(config.simultaneousRooms, visibleRooms.length); // Get current usernames that should be animated const currentUsernames = []; for (let i = 0; i < roomsToAnimate; i++) { const room = visibleRooms[i]; if (room) { const roomLink = room.querySelector('a[data-room]'); const username = roomLink ? roomLink.getAttribute('data-room') : null; if (username) { currentUsernames.push({ room, username }); } } } // Remove previews for rooms that are no longer being animated roomElements.forEach((room) => { const existingPreview = room.querySelector('.direct-video-preview'); if (existingPreview) { const roomLink = room.querySelector('a[data-room]'); const username = roomLink ? roomLink.getAttribute('data-room') : null; // Only remove if this room is not in current animation list if (!currentUsernames.some(item => item.username === username)) { removeDirectVideoPreview(room); } } }); // Create or update previews for current rooms currentUsernames.forEach(({ room, username }) => { const existingPreview = room.querySelector('.direct-video-preview'); if (existingPreview) { // Force update by creating a new image and replacing when loaded const cacheBuster = Math.random(); const thumbnailUrl = `https://thumb.live.mmcdn.com/minifwap/${username}.jpg?${cacheBuster}`; // Create a new image to preload const newImg = document.createElement('img'); newImg.className = 'direct-video-preview'; newImg.style.cssText = ` position: absolute !important; top: 0 !important; left: 0 !important; width: 100% !important; height: 100% !important; object-fit: cover !important; z-index: 9999 !important; pointer-events: none !important; `; newImg.onload = () => { // Replace the old image with the new one only after it's loaded if (existingPreview.parentNode) { existingPreview.parentNode.replaceChild(newImg, existingPreview); } }; newImg.onerror = () => { newImg.remove(); }; newImg.src = thumbnailUrl; } else { // Create new preview createDirectVideoPreview(room, username); } }); updateAnimatingDisplay(); } // Restart animation function restartAnimation() { console.log(`🔄 Restarting invisible animation: ${config.simultaneousRooms} rooms, ${config.animationSpeed}ms interval`); if (animationInterval) { clearInterval(animationInterval); animationInterval = null; } if (config.enabled) { animationInterval = setInterval(animateMultipleRooms, config.animationSpeed); animateMultipleRooms(); // Start immediately } } // Start animation function startAnimation() { if (isCompletelyDisabled) return; console.log('🚀 Starting invisible multi-room animation...'); if (!findRoomElements()) { alert(`No room elements found! Try these steps: 1. Make sure you're on a page with room listings 2. Try the "Refresh" button after the page loads completely`); return; } console.log(`✅ Found ${roomElements.length} rooms, starting INVISIBLE animation (no scrolling, no borders)`); config.enabled = true; animationInterval = setInterval(animateMultipleRooms, config.animationSpeed); animateMultipleRooms(); // Start immediately updateUI(); } // Stop animation function stopAnimation() { console.log('⏹️ Stopping invisible animation...'); config.enabled = false; if (animationInterval) { clearInterval(animationInterval); animationInterval = null; } // Remove all previews roomElements.forEach(room => { removeDirectVideoPreview(room); }); updateUI(); updateAnimatingDisplay(); } // Toggle animation function toggleAnimation() { if (config.enabled) { stopAnimation(); } else { startAnimation(); } } // Update UI elements function updateUI() { try { const toggleBtn = document.getElementById('toggle-animation'); const status = document.getElementById('status'); if (toggleBtn) { toggleBtn.textContent = config.enabled ? 'Stop' : 'Start'; toggleBtn.style.background = config.enabled ? '#dc3545' : '#ff6b35'; } if (status) { status.textContent = config.enabled ? 'Running' : 'Stopped'; status.style.color = config.enabled ? '#4CAF50' : '#ccc'; } } catch (error) { console.error('❌ Error updating UI:', error); } } // Initialize when page loads function init() { console.log('🚀 Multi-Room Animator v2.3 (with drag and minimize) starting...'); if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', init); return; } simpleLoad(); setTimeout(() => { createControlPanel(); if (findRoomElements()) { // Auto-start animation after everything is set up setTimeout(() => { startAnimation(); }, 500); } }, 1000); } // Handle navigation changes let currentUrl = location.href; mutationObserver = new MutationObserver(() => { if (isCompletelyDisabled) return; // Check for URL changes (tab changes) if (location.href !== currentUrl) { currentUrl = location.href; console.log('🔄 Page navigation detected, updating rooms...'); setTimeout(() => { // Re-find rooms on the new page if (findRoomElements()) { console.log('✅ Rooms updated after navigation'); } else { console.log('⚠️ No rooms found after navigation'); } }, 1500); // Wait a bit longer for content to load } // Also check for content changes (rooms loading/changing) const roomContainers = document.querySelectorAll('li[class*="room"], .roomCard'); if (roomContainers.length !== roomElements.length && roomContainers.length > 0) { console.log(`🔄 Room count changed: ${roomElements.length} → ${roomContainers.length}`); setTimeout(() => { findRoomElements(); }, 500); } }); mutationObserver.observe(document, { subtree: true, childList: true }); // Start the script init(); })();