// ==UserScript== // @name cams // @namespace http://tampermonkey.net/ // @version 1.2.3 // @description Set maxDockedCamsForUser, keep-alive, and multi-poke // @author You // @match https://chat.fabswingers.com/* // @updateURL https://git.upto.im/geekery/scripts/raw/branch/main/meta.js // @downloadURL https://git.upto.im/geekery/scripts/raw/branch/main/cams.user.js // @grant none // @run-at document-idle // ==/UserScript== (function() { 'use strict'; const BUTTON_ID = 'maxDockedCamsButton_SINGLETON'; const KEEPALIVE_BUTTON_ID = 'keepAliveButton_SINGLETON'; const MULTIPOKE_BUTTON_ID = 'multiPokeButton_SINGLETON'; const LOCK_KEY = 'maxDockedCams_scriptLock'; const KEEPALIVE_SETTINGS_KEY = 'keepAlive_settings'; const MULTIPOKE_SETTINGS_KEY = 'multiPoke_settings'; const BUTTON_POSITIONS_KEY = 'buttonPositions'; // Load saved button positions function loadButtonPositions() { try { const saved = localStorage.getItem(BUTTON_POSITIONS_KEY); if (saved) { return JSON.parse(saved); } } catch (e) { console.log('Error loading button positions', e); } return {}; } function saveButtonPosition(buttonId, x, y) { const positions = loadButtonPositions(); positions[buttonId] = { x, y }; localStorage.setItem(BUTTON_POSITIONS_KEY, JSON.stringify(positions)); } function makeDraggable(button) { let isDragging = false; let wasDragged = false; let startX, startY, initialX, initialY; button.style.cursor = 'move'; button.addEventListener('mousedown', function(e) { if (e.button !== 0) return; // Only left click isDragging = true; wasDragged = false; startX = e.clientX; startY = e.clientY; initialX = button.offsetLeft; initialY = button.offsetTop; e.preventDefault(); }); document.addEventListener('mousemove', function(e) { if (!isDragging) return; const deltaX = e.clientX - startX; const deltaY = e.clientY - startY; // Only count as drag if moved more than 5px if (Math.abs(deltaX) > 5 || Math.abs(deltaY) > 5) { wasDragged = true; } let newX = initialX + deltaX; let newY = initialY + deltaY; // Keep within viewport newX = Math.max(0, Math.min(newX, window.innerWidth - button.offsetWidth)); newY = Math.max(0, Math.min(newY, window.innerHeight - button.offsetHeight)); button.style.left = newX + 'px'; button.style.top = newY + 'px'; button.style.right = 'auto'; }); document.addEventListener('mouseup', function(e) { if (isDragging) { isDragging = false; if (wasDragged) { saveButtonPosition(button.id, button.offsetLeft, button.offsetTop); } } }); // Prevent click event if we were dragging button.addEventListener('click', function(e) { if (wasDragged) { e.preventDefault(); e.stopPropagation(); wasDragged = false; } }, true); // Apply saved position if exists const positions = loadButtonPositions(); if (positions[button.id]) { button.style.left = positions[button.id].x + 'px'; button.style.top = positions[button.id].y + 'px'; button.style.right = 'auto'; } } // Public room names to match against const PUBLIC_ROOMS = [ 'General Chat', 'Directing Room', 'Directing Room #2', 'Directing Room #3', 'Directing Room #4', 'Directing Room #5', 'Directing Room #6', 'Site Supporters', 'Verified Users', 'Scotland Swingers', 'Northern Swingers', 'South East Swing', 'South West Swing', 'Midlands Swing', 'East Anglia', 'Ireland Swingers', 'London Swingers', 'Wales Swingers', 'USA Swingers', 'Canadian Swingers', 'Bisexual Chat', 'Bi Directing Room' ]; const DEFAULT_INTERVAL = 10; // minutes const DEFAULT_POKE_COUNT = 10; const POKE_DELAY = 150; // ms between pokes let keepAliveInterval = null; let isKeepAliveRunning = false; let keepAliveSettings = loadKeepAliveSettings(); let multiPokeEnabled = loadMultiPokeSettings(); function loadMultiPokeSettings() { return localStorage.getItem(MULTIPOKE_SETTINGS_KEY) !== 'false'; } function saveMultiPokeSettings() { localStorage.setItem(MULTIPOKE_SETTINGS_KEY, multiPokeEnabled.toString()); } function loadKeepAliveSettings() { try { const saved = localStorage.getItem(KEEPALIVE_SETTINGS_KEY); if (saved) { return JSON.parse(saved); } } catch (e) { console.log('KeepAlive: Error loading settings', e); } return { intervalMinutes: DEFAULT_INTERVAL }; } function saveKeepAliveSettings() { localStorage.setItem(KEEPALIVE_SETTINGS_KEY, JSON.stringify(keepAliveSettings)); } // Try to acquire lock const lockValue = Date.now().toString(); if (localStorage.getItem(LOCK_KEY)) { console.log('MaxDockedCams: Another instance already running, exiting'); return; } localStorage.setItem(LOCK_KEY, lockValue); window.addEventListener('beforeunload', function() { if (localStorage.getItem(LOCK_KEY) === lockValue) { localStorage.removeItem(LOCK_KEY); } }); // Find a public room that the user has joined function findPublicRoom() { // Try multiple selectors for chat windows const chatWindows = document.querySelectorAll('[id^="CHATWINDOW"], [id^="ChatWindow"], [id^="chatwindow"], .x-window'); for (const win of chatWindows) { // Try multiple selectors for the title const titleEl = win.querySelector('.x-window-header-text') || win.querySelector('.x-window-header span') || win.querySelector('[class*="header"] span'); if (titleEl) { const title = titleEl.textContent.trim(); for (const publicRoom of PUBLIC_ROOMS) { // Use startsWith to match "General Chat" in "General Chat (45)" if (title.startsWith(publicRoom)) { // Extract room ID from window ID (format: CHATWINDOW08967111-b7ae-496c-...) const roomId = win.id.replace(/^CHATWINDOW/i, '').replace(/^ChatWindow_?/i, ''); if (roomId) { console.log(`KeepAlive: Found public room "${title}" with ID ${roomId}`); return { roomId, title, windowId: win.id }; } } } } } // Fallback: check RoomListStore if (typeof RoomListStore !== 'undefined' && RoomListStore.data) { const rooms = RoomListStore.data.items || []; for (const room of rooms) { const roomData = room.data || room; const name = roomData.Name || ''; for (const publicRoom of PUBLIC_ROOMS) { if (name.startsWith(publicRoom)) { console.log(`KeepAlive: Found public room "${name}" with ID ${roomData.ID}`); return { roomId: roomData.ID, title: name }; } } } } // Debug: log what windows we can find console.log('KeepAlive: Debug - searching for windows...'); document.querySelectorAll('.x-window').forEach(w => { console.log('KeepAlive: Found window:', w.id, w.querySelector('.x-window-header-text')?.textContent); }); return null; } function getUserId() { if (typeof userID !== 'undefined') return userID; if (typeof window.userID !== 'undefined') return window.userID; if (typeof _currentUserID !== 'undefined') return _currentUserID; if (typeof currentUserID !== 'undefined') return currentUserID; return null; } function getAuthCode() { if (typeof auth_code !== 'undefined') return auth_code; if (typeof window.auth_code !== 'undefined') return window.auth_code; if (typeof _authCode !== 'undefined') return _authCode; if (typeof authCode !== 'undefined') return authCode; return null; } function sendKeepAlive() { const room = findPublicRoom(); if (!room) { console.log('KeepAlive: No public room found'); return false; } // Find the chat input field for this room // Look for input field within the chat window const chatWindow = room.windowId ? document.getElementById(room.windowId) : document.getElementById('CHATWINDOW' + room.roomId) || document.getElementById('ChatWindow_' + room.roomId) || document.querySelector(`[id*="${room.roomId}"].x-window`); if (!chatWindow) { console.log('KeepAlive: Could not find chat window element'); return sendKeepAliveViaAPI(room); } // Find the text input field - usually a textarea or input within the window const inputField = chatWindow.querySelector('textarea') || chatWindow.querySelector('input[type="text"]') || chatWindow.querySelector('.x-form-text'); if (!inputField) { console.log('KeepAlive: Could not find input field, trying API method'); return sendKeepAliveViaAPI(room); } // Store current value, set our message, trigger submit, restore const originalValue = inputField.value; inputField.value = ' '; inputField.focus(); // Trigger input event inputField.dispatchEvent(new Event('input', { bubbles: true })); inputField.dispatchEvent(new Event('change', { bubbles: true })); // Simulate Enter key press to submit const enterEvent = new KeyboardEvent('keydown', { key: 'Enter', code: 'Enter', keyCode: 13, which: 13, bubbles: true }); inputField.dispatchEvent(enterEvent); const enterPress = new KeyboardEvent('keypress', { key: 'Enter', code: 'Enter', keyCode: 13, which: 13, bubbles: true }); inputField.dispatchEvent(enterPress); const enterUp = new KeyboardEvent('keyup', { key: 'Enter', code: 'Enter', keyCode: 13, which: 13, bubbles: true }); inputField.dispatchEvent(enterUp); console.log(`KeepAlive: Simulated input to "${room.title}" at ${new Date().toLocaleTimeString()}`); return true; } function sendKeepAliveViaAPI(room) { const userId = getUserId(); if (!userId) { console.log('KeepAlive: User ID not found'); return false; } const authCodeVal = getAuthCode(); if (typeof Coolite !== 'undefined' && Coolite.AjaxMethods && Coolite.AjaxMethods.AddChatLine) { Coolite.AjaxMethods.AddChatLine( room.roomId, ' ', userId, window._ipaddress, window._urlwebchatid, window._urlwebchatkey, false, false, false, '', authCodeVal, { success: function() { console.log(`KeepAlive: Sent via API to "${room.title}" at ${new Date().toLocaleTimeString()}`); }, failure: function(error) { console.log('KeepAlive: Failed to send message', error); } } ); return true; } else { console.log('KeepAlive: Coolite.AjaxMethods.AddChatLine not available'); return false; } } function startKeepAlive() { if (isKeepAliveRunning) return; const intervalMs = keepAliveSettings.intervalMinutes * 60 * 1000; keepAliveInterval = setInterval(sendKeepAlive, intervalMs); isKeepAliveRunning = true; updateKeepAliveButtonState(); console.log(`KeepAlive: Started with ${keepAliveSettings.intervalMinutes} minute interval`); sendKeepAlive(); } function stopKeepAlive() { if (keepAliveInterval) { clearInterval(keepAliveInterval); keepAliveInterval = null; } isKeepAliveRunning = false; updateKeepAliveButtonState(); console.log('KeepAlive: Stopped'); } function updateKeepAliveButtonState() { const button = document.getElementById(KEEPALIVE_BUTTON_ID); if (button) { if (isKeepAliveRunning) { button.style.backgroundColor = '#f44336'; button.textContent = `Keep Alive ON (${keepAliveSettings.intervalMinutes}m)`; } else { button.style.backgroundColor = '#2196F3'; button.textContent = 'Keep Alive OFF'; } } } function showKeepAliveDialog() { const overlay = document.createElement('div'); overlay.style.cssText = 'position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.7); z-index: 9999999; display: flex; align-items: center; justify-content: center;'; const dialog = document.createElement('div'); dialog.style.cssText = 'background: white; padding: 20px; border-radius: 8px; box-shadow: 0 4px 20px rgba(0,0,0,0.5); min-width: 300px;'; dialog.innerHTML = `
Interval (minutes):
Status: ${isKeepAliveRunning ? 'Running' : 'Stopped'}
Enter a number: