824 lines
31 KiB
JavaScript
824 lines
31 KiB
JavaScript
// ==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 = `
|
|
<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;';
|
|
|
|
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;">Set MaxDockedCams</h3>
|
|
<p style="margin: 0 0 10px 0; color: #666;">Enter a number:</p>
|
|
<input type="number" id="maxDockedInput" style="width: 100%; padding: 8px; border: 1px solid #ccc; border-radius: 4px; font-size: 14px; box-sizing: border-box;" placeholder="Enter number" />
|
|
<div style="margin-top: 15px; text-align: right;">
|
|
<button id="cancelBtn" style="padding: 8px 15px; margin-right: 10px; background: #ccc; color: #333; border: none; border-radius: 4px; cursor: pointer; font-size: 14px;">Cancel</button>
|
|
<button id="okBtn" style="padding: 8px 15px; background: #4CAF50; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 14px;">OK</button>
|
|
</div>
|
|
</div>
|
|
`;
|
|
|
|
overlay.appendChild(dialog);
|
|
document.body.appendChild(overlay);
|
|
|
|
const input = document.getElementById('maxDockedInput');
|
|
const okBtn = document.getElementById('okBtn');
|
|
const cancelBtn = document.getElementById('cancelBtn');
|
|
|
|
setTimeout(() => input.focus(), 100);
|
|
|
|
okBtn.onclick = function() {
|
|
const value = input.value;
|
|
if (value === '') {
|
|
alert('Please enter a number');
|
|
return;
|
|
}
|
|
|
|
const number = parseInt(value, 10);
|
|
if (!isNaN(number)) {
|
|
window.maxDockedCamsForUser = number;
|
|
console.log(`maxDockedCamsForUser set to: ${number}`);
|
|
overlay.remove();
|
|
alert(`maxDockedCamsForUser successfully set to ${number}`);
|
|
} else {
|
|
alert('Please enter a valid number');
|
|
}
|
|
};
|
|
|
|
cancelBtn.onclick = function() {
|
|
console.log('User cancelled');
|
|
overlay.remove();
|
|
};
|
|
|
|
input.onkeypress = function(e) {
|
|
if (e.key === 'Enter') {
|
|
okBtn.click();
|
|
}
|
|
};
|
|
|
|
overlay.onkeydown = function(e) {
|
|
if (e.key === 'Escape') {
|
|
cancelBtn.click();
|
|
}
|
|
};
|
|
}
|
|
|
|
// 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());
|
|
|
|
// 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);';
|
|
|
|
maxDockedButton.addEventListener('click', function(e) {
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
showCustomPrompt();
|
|
});
|
|
|
|
document.body.appendChild(maxDockedButton);
|
|
makeDraggable(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);
|
|
makeDraggable(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);
|
|
makeDraggable(multiPokeButton);
|
|
updateMultiPokeButtonState();
|
|
console.log('MultiPoke button created');
|
|
|
|
// 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 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);
|
|
|
|
})();
|