From bbe1418c728ffe4660bb778ba606863857557c2c Mon Sep 17 00:00:00 2001 From: Darren Paxton Date: Wed, 10 Dec 2025 14:25:35 -0500 Subject: [PATCH] Added multipoke and timeout --- cams.js | 647 +++++++++++++++++++++++++++++++++++++++++++++++++++++--- meta.js | 15 +- 2 files changed, 631 insertions(+), 31 deletions(-) diff --git a/cams.js b/cams.js index 062d66f..e9b42a4 100644 --- a/cams.js +++ b/cams.js @@ -1,8 +1,8 @@ // ==UserScript== // @name cams // @namespace http://tampermonkey.net/ -// @version 1.0.1 -// @description Set maxDockedCamsForUser with a custom dialog +// @version 1.2.0 +// @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 @@ -15,7 +15,71 @@ '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'; + + // 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(); @@ -33,17 +97,464 @@ } }); - // Create custom dialog - function showCustomPrompt() { - // Create overlay + // 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_"], .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 + const idMatch = win.id.match(/(\d+)/); + const roomId = idMatch ? idMatch[1] : win.id.replace(/[^\d]/g, ''); + if (roomId) { + console.log(`KeepAlive: Found public room "${title}" with ID ${roomId}`); + return { roomId, title }; + } + } + } + } + } + + // 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 = 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 = ` +
+

Keep Alive Settings

+

Interval (minutes):

+ +

Status: ${isKeepAliveRunning ? 'Running' : 'Stopped'}

+
+ + + +
+
+ `; + + overlay.appendChild(dialog); + document.body.appendChild(overlay); + + const input = document.getElementById('keepAliveIntervalInput'); + const saveBtn = document.getElementById('keepAliveSaveBtn'); + const cancelBtn = document.getElementById('keepAliveCancelBtn'); + const toggleBtn = document.getElementById('keepAliveToggleBtn'); + + setTimeout(() => input.focus(), 100); + + saveBtn.onclick = function() { + const value = parseInt(input.value, 10); + if (value >= 1 && value <= 30) { + keepAliveSettings.intervalMinutes = value; + saveKeepAliveSettings(); + + if (isKeepAliveRunning) { + stopKeepAlive(); + startKeepAlive(); + } + + updateKeepAliveButtonState(); + overlay.remove(); + } else { + alert('Please enter a number between 1 and 30'); + } + }; + + cancelBtn.onclick = function() { + overlay.remove(); + }; + + toggleBtn.onclick = function() { + if (isKeepAliveRunning) { + stopKeepAlive(); + } else { + const value = parseInt(input.value, 10); + if (value >= 1 && value <= 30) { + keepAliveSettings.intervalMinutes = value; + saveKeepAliveSettings(); + } + startKeepAlive(); + } + overlay.remove(); + }; + + input.onkeypress = function(e) { + if (e.key === 'Enter') { + saveBtn.click(); + } + }; + + overlay.onkeydown = function(e) { + if (e.key === 'Escape') { + cancelBtn.click(); + } + }; + } + + // ==================== MULTI-POKE FUNCTIONALITY ==================== + + function performMultiPoke(roomId, userId, buttonElement) { + const originalText = buttonElement.textContent; + let pokeCount = 0; + const totalPokes = DEFAULT_POKE_COUNT; + + // Disable the button during multi-poke + buttonElement.style.opacity = '0.5'; + buttonElement.style.pointerEvents = 'none'; + + function sendPoke() { + if (pokeCount < totalPokes) { + try { + if (typeof window.PK === 'function') { + window.PK(roomId, userId); + } else if (typeof Coolite !== 'undefined' && Coolite.AjaxMethods && Coolite.AjaxMethods.Poke) { + Coolite.AjaxMethods.Poke(roomId, '', userId, '', ''); + } else { + const tempPokeLink = document.createElement('a'); + tempPokeLink.href = `javascript:PK('${roomId}','${userId}')`; + tempPokeLink.style.display = 'none'; + document.body.appendChild(tempPokeLink); + tempPokeLink.click(); + document.body.removeChild(tempPokeLink); + } + } catch (error) { + console.log('Poke error:', error); + } + + pokeCount++; + buttonElement.textContent = `${totalPokes - pokeCount}x`; + + if (pokeCount < totalPokes) { + setTimeout(sendPoke, POKE_DELAY); + } else { + setTimeout(() => { + buttonElement.textContent = originalText; + buttonElement.style.opacity = ''; + buttonElement.style.pointerEvents = ''; + }, 500); + } + } + } + + sendPoke(); + } + + function addMultiPokeButtons() { + if (!multiPokeEnabled) return; + + const userRows = document.querySelectorAll('.ur'); + + userRows.forEach(userRow => { + const allDiv = userRow.querySelector('.all'); + if (!allDiv) return; + if (allDiv.innerHTML.trim() === '') return; + if (allDiv.querySelector('.multi-poke-btn')) return; + + const pokeButton = allDiv.querySelector('a[href*="PK("]'); + if (!pokeButton) return; + + const pokeHref = pokeButton.getAttribute('href'); + const match = pokeHref.match(/PK\('([^']+)','([^']+)'\)/); + if (!match) return; + + const roomId = match[1]; + const odlUserId = match[2]; + + const multiPokeBtn = document.createElement('a'); + multiPokeBtn.className = 'multi-poke-btn'; + multiPokeBtn.href = 'javascript:void(0)'; + multiPokeBtn.textContent = '10x'; + multiPokeBtn.title = 'Send 10 pokes'; + multiPokeBtn.setAttribute('data-multi-poke', 'true'); + multiPokeBtn.setAttribute('data-user-id', odlUserId); + + multiPokeBtn.addEventListener('click', function(e) { + e.preventDefault(); + performMultiPoke(roomId, odlUserId, this); + }); + + const space = document.createTextNode('\u00A0'); + pokeButton.parentNode.insertBefore(space, pokeButton.nextSibling); + pokeButton.parentNode.insertBefore(multiPokeBtn, space.nextSibling); + }); + } + + function removeMultiPokeButtons() { + const multiPokeElements = document.querySelectorAll('.multi-poke-btn, [data-multi-poke="true"]'); + multiPokeElements.forEach(element => { + if (element.previousSibling && element.previousSibling.nodeType === Node.TEXT_NODE) { + element.previousSibling.remove(); + } + element.remove(); + }); + } + + function toggleMultiPoke() { + multiPokeEnabled = !multiPokeEnabled; + saveMultiPokeSettings(); + updateMultiPokeButtonState(); + + if (multiPokeEnabled) { + addMultiPokeButtons(); + } else { + removeMultiPokeButtons(); + } + + console.log('Multi-poke', multiPokeEnabled ? 'enabled' : 'disabled'); + } + + function updateMultiPokeButtonState() { + const button = document.getElementById(MULTIPOKE_BUTTON_ID); + if (button) { + if (multiPokeEnabled) { + button.style.backgroundColor = '#ff6b35'; + button.textContent = '10x ON'; + } else { + button.style.backgroundColor = '#6c757d'; + button.textContent = '10x OFF'; + } + } + } + + function observeUserList() { + const targetNode = document.querySelector('div[id*="CHATUSERLABEL"]') || + document.querySelector('.x-panel-body') || + document.body; + + const observer = new MutationObserver(function(mutations) { + let shouldUpdate = false; + mutations.forEach(function(mutation) { + if (mutation.type === 'childList') { + mutation.addedNodes.forEach(node => { + if (node.nodeType === Node.ELEMENT_NODE && + (node.classList && node.classList.contains('ur') || node.querySelector && node.querySelector('.ur'))) { + shouldUpdate = true; + } + }); + } + if (mutation.type === 'childList' && mutation.target.classList && mutation.target.classList.contains('ur')) { + shouldUpdate = true; + } + }); + + if (shouldUpdate && multiPokeEnabled) { + clearTimeout(window.multiPokeUpdateDebounce); + window.multiPokeUpdateDebounce = setTimeout(() => { + addMultiPokeButtons(); + }, 200); + } + }); + + observer.observe(targetNode, { + childList: true, + subtree: true + }); + } + + // ==================== END MULTI-POKE ==================== + + // Create custom dialog for MaxDockedCams + function showCustomPrompt() { 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;'; - // Create dialog box 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;'; - // Add content dialog.innerHTML = `

Set MaxDockedCams

@@ -63,10 +574,8 @@ const okBtn = document.getElementById('okBtn'); const cancelBtn = document.getElementById('cancelBtn'); - // Focus input setTimeout(() => input.focus(), 100); - // Handle OK button okBtn.onclick = function() { const value = input.value; if (value === '') { @@ -85,20 +594,17 @@ } }; - // Handle Cancel button cancelBtn.onclick = function() { console.log('User cancelled'); overlay.remove(); }; - // Handle Enter key input.onkeypress = function(e) { if (e.key === 'Enter') { okBtn.click(); } }; - // Handle Escape key overlay.onkeydown = function(e) { if (e.key === 'Escape') { cancelBtn.click(); @@ -106,34 +612,125 @@ }; } - // Wait and create button + // Wait and create buttons setTimeout(function() { + // Cleanup existing buttons document.querySelectorAll(`[id^="maxDockedCamsButton"]`).forEach(btn => btn.remove()); + document.querySelectorAll(`[id^="keepAliveButton"]`).forEach(btn => btn.remove()); - const button = document.createElement('button'); - button.id = BUTTON_ID; - button.textContent = 'Set MaxDockedCams'; - button.type = 'button'; - button.style.cssText = 'position: fixed; top: 10px; right: 10px; z-index: 999999; padding: 8px 12px; background-color: #4CAF50; color: white; border: none; border-radius: 4px; cursor: pointer; font-weight: bold; box-shadow: 0 2px 5px rgba(0,0,0,0.3);'; + // MaxDockedCams button + const maxDockedButton = document.createElement('button'); + maxDockedButton.id = BUTTON_ID; + maxDockedButton.textContent = 'Set MaxDockedCams'; + maxDockedButton.type = 'button'; + maxDockedButton.style.cssText = 'position: fixed; top: 10px; right: 10px; z-index: 999999; padding: 8px 12px; background-color: #4CAF50; color: white; border: none; border-radius: 4px; cursor: pointer; font-weight: bold; box-shadow: 0 2px 5px rgba(0,0,0,0.3);'; - button.addEventListener('click', function(e) { + maxDockedButton.addEventListener('click', function(e) { e.preventDefault(); e.stopPropagation(); showCustomPrompt(); }); - document.body.appendChild(button); + document.body.appendChild(maxDockedButton); console.log('MaxDockedCams button created'); + // Keep Alive button + const keepAliveButton = document.createElement('button'); + keepAliveButton.id = KEEPALIVE_BUTTON_ID; + keepAliveButton.type = 'button'; + keepAliveButton.style.cssText = 'position: fixed; top: 10px; right: 170px; z-index: 999999; padding: 8px 12px; background-color: #2196F3; color: white; border: none; border-radius: 4px; cursor: pointer; font-weight: bold; box-shadow: 0 2px 5px rgba(0,0,0,0.3);'; + + keepAliveButton.addEventListener('click', function(e) { + e.preventDefault(); + e.stopPropagation(); + showKeepAliveDialog(); + }); + + document.body.appendChild(keepAliveButton); + updateKeepAliveButtonState(); + console.log('KeepAlive button created'); + + // Multi-Poke toggle button + document.querySelectorAll(`[id^="multiPokeButton"]`).forEach(btn => btn.remove()); + const multiPokeButton = document.createElement('button'); + multiPokeButton.id = MULTIPOKE_BUTTON_ID; + multiPokeButton.type = 'button'; + multiPokeButton.style.cssText = 'position: fixed; top: 10px; right: 340px; z-index: 999999; padding: 8px 12px; background-color: #ff6b35; color: white; border: none; border-radius: 4px; cursor: pointer; font-weight: bold; box-shadow: 0 2px 5px rgba(0,0,0,0.3);'; + + multiPokeButton.addEventListener('click', function(e) { + e.preventDefault(); + e.stopPropagation(); + toggleMultiPoke(); + }); + + document.body.appendChild(multiPokeButton); + updateMultiPokeButtonState(); + console.log('MultiPoke button created'); + + // Auto-start keep-alive + setTimeout(function() { + startKeepAlive(); + }, 2000); + + // Initialize multi-poke if enabled + setTimeout(function() { + if (multiPokeEnabled) { + addMultiPokeButtons(); + } + observeUserList(); + + // Periodic check for new users + setInterval(() => { + if (multiPokeEnabled) { + addMultiPokeButtons(); + } + }, 2000); + }, 1000); + + // Cleanup duplicate buttons periodically setInterval(function() { - const buttons = document.querySelectorAll(`#${BUTTON_ID}`); - if (buttons.length > 1) { - for (let i = 1; i < buttons.length; i++) { - buttons[i].remove(); + const maxDockedButtons = document.querySelectorAll(`#${BUTTON_ID}`); + if (maxDockedButtons.length > 1) { + for (let i = 1; i < maxDockedButtons.length; i++) { + maxDockedButtons[i].remove(); + } + } + + const keepAliveButtons = document.querySelectorAll(`#${KEEPALIVE_BUTTON_ID}`); + if (keepAliveButtons.length > 1) { + for (let i = 1; i < keepAliveButtons.length; i++) { + keepAliveButtons[i].remove(); + } + } + + const multiPokeButtons = document.querySelectorAll(`#${MULTIPOKE_BUTTON_ID}`); + if (multiPokeButtons.length > 1) { + for (let i = 1; i < multiPokeButtons.length; i++) { + multiPokeButtons[i].remove(); } } }, 500); }, 100); -})(); \ No newline at end of file + // Add CSS for multi-poke buttons + const style = document.createElement('style'); + style.textContent = ` + .multi-poke-btn { + background-color: #ff6b35 !important; + color: white !important; + padding: 1px 4px !important; + border-radius: 3px !important; + text-decoration: none !important; + font-weight: bold !important; + font-size: 10px !important; + margin-left: 2px !important; + } + .multi-poke-btn:hover { + background-color: #ff8c42 !important; + color: white !important; + } + `; + document.head.appendChild(style); + +})(); diff --git a/meta.js b/meta.js index 8efcca0..1741f3a 100644 --- a/meta.js +++ b/meta.js @@ -1,9 +1,12 @@ // ==UserScript== -// @name Button for more -// @namespace https://git.upto.im/geekery/ -// @version 1.0.1 -// @description Button to add more portals -// @author TheMonitor +// @name cams +// @namespace http://tampermonkey.net/ +// @version 1.2.0 +// @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.js // @grant none -// ==/UserScript== \ No newline at end of file +// @run-at document-idle +// ==/UserScript==