Added multipoke and timeout

This commit is contained in:
2025-12-10 14:25:35 -05:00
parent 814c245cc3
commit bbe1418c72
2 changed files with 631 additions and 31 deletions

647
cams.js
View File

@@ -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 = `
<div style="font-family: Arial, sans-serif;">
<h3 style="margin: 0 0 15px 0; color: #333;">Keep Alive Settings</h3>
<p style="margin: 0 0 10px 0; color: #666;">Interval (minutes):</p>
<input type="number" id="keepAliveIntervalInput" value="${keepAliveSettings.intervalMinutes}" min="1" max="30" style="width: 100%; padding: 8px; border: 1px solid #ccc; border-radius: 4px; font-size: 14px; box-sizing: border-box;" />
<p style="margin: 10px 0; color: #888; font-size: 12px;">Status: ${isKeepAliveRunning ? 'Running' : 'Stopped'}</p>
<div style="margin-top: 15px; display: flex; gap: 10px; justify-content: flex-end; flex-wrap: wrap;">
<button id="keepAliveCancelBtn" style="padding: 8px 15px; background: #ccc; color: #333; border: none; border-radius: 4px; cursor: pointer; font-size: 14px;">Cancel</button>
<button id="keepAliveToggleBtn" style="padding: 8px 15px; background: ${isKeepAliveRunning ? '#f44336' : '#4CAF50'}; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 14px;">${isKeepAliveRunning ? 'Stop' : 'Start'}</button>
<button id="keepAliveSaveBtn" style="padding: 8px 15px; background: #2196F3; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 14px;">Save</button>
</div>
</div>
`;
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 = `
<div style="font-family: Arial, sans-serif;">
<h3 style="margin: 0 0 15px 0; color: #333;">Set MaxDockedCams</h3>
@@ -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);
})();
// 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);
})();

15
meta.js
View File

@@ -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==
// @run-at document-idle
// ==/UserScript==