您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Adds camera group management with persistent settings, minimizable UI, and person detection toggle for Wyze Events page
// ==UserScript== // @name Enhanced Wyze Group Selector & Camera Management // @namespace http://ptelectronics.net // @version 2.3 // @description Adds camera group management with persistent settings, minimizable UI, and person detection toggle for Wyze Events page // @author Math Shamenson // @match https://my.wyze.com/events* // @grant none // @license MIT // @run-at document-idle // @homepageURL https://greasyfork.org/scripts/SCRIPT_ID // @supportURL https://greasyfork.org/scripts/SCRIPT_ID/feedback // ==/UserScript== (function () { 'use strict'; // Enhanced state management with validation class GroupManager { constructor() { this.groups = this.loadGroups(); this.uiState = this.loadUIState(); } loadGroups() { try { const stored = localStorage.getItem('cameraGroups'); return stored ? JSON.parse(stored) : { "Group 1: Back Yard": ["2CAA8E86C673", "2CAA8E09C409"], "Group 2: Front Yard": ["2CAA8E59E1AA", "2CAA8E6E0638"], "Group 3: Misc Cameras": ["2CAA8E778175", "2CAA8E52C9FA"] }; } catch (error) { console.error('Error loading groups:', error); return {}; } } loadUIState() { try { const stored = localStorage.getItem('cameraGroupsUIState'); return stored ? JSON.parse(stored) : { minimized: false }; } catch (error) { console.error('Error loading UI state:', error); return { minimized: false }; } } saveUIState(state) { try { localStorage.setItem('cameraGroupsUIState', JSON.stringify(state)); this.uiState = state; } catch (error) { console.error('Error saving UI state:', error); } } saveGroups() { try { localStorage.setItem('cameraGroups', JSON.stringify(this.groups)); } catch (error) { console.error('Error saving groups:', error); alert('Failed to save groups. Please check console for details.'); } } addGroup(name, cameras) { if (!name || !cameras || !Array.isArray(cameras)) { throw new Error('Invalid group data'); } this.groups[name] = cameras; this.saveGroups(); } updateGroup(oldName, newName, cameras) { if (!oldName || !newName || !cameras || !Array.isArray(cameras)) { throw new Error('Invalid group update data'); } delete this.groups[oldName]; this.groups[newName] = cameras; this.saveGroups(); } deleteGroup(name) { if (!name || !this.groups[name]) { throw new Error('Invalid group name'); } delete this.groups[name]; this.saveGroups(); } } // UI Component with improved styling class ControlPanel { constructor(groupManager) { this.groupManager = groupManager; this.position = this.loadPosition(); this.createPanel(); // Initialize minimized state from persistent storage if (this.groupManager.uiState.minimized) { this.toggleMinimize(false); // Don't save state on initial load } } loadPosition() { const stored = localStorage.getItem('controlPanelPosition'); if (stored) { return JSON.parse(stored); } // Calculate initial position from right side const initialLeft = window.innerWidth - 260; // 250px width + 10px margin return { top: '50px', left: `${initialLeft}px` }; } savePosition() { localStorage.setItem('controlPanelPosition', JSON.stringify({ top: this.container.style.top, left: this.container.style.left })); } createPanel() { const existingPanel = document.getElementById('wyze-control-panel'); if (existingPanel) existingPanel.remove(); this.container = document.createElement('div'); this.container.id = 'wyze-control-panel'; this.applyStyles(); this.setupDraggable(); this.createContent(); document.body.appendChild(this.container); } applyStyles() { Object.assign(this.container.style, { position: 'fixed', top: this.position.top, left: this.position.left, background: 'white', border: '2px solid #4a90e2', borderRadius: '8px', padding: '15px', zIndex: '10000', maxHeight: '90vh', overflowY: 'auto', boxShadow: '0 4px 6px rgba(0, 0, 0, 0.1)', width: '250px' // Fixed width instead of minWidth }); } setupDraggable() { let isDragging = false; let currentX; let currentY; let initialX; let initialY; this.container.addEventListener('mousedown', (e) => { if (e.target.tagName === 'BUTTON') return; isDragging = true; initialX = e.clientX - this.container.offsetLeft; initialY = e.clientY - this.container.offsetTop; }); document.addEventListener('mousemove', (e) => { if (!isDragging) return; e.preventDefault(); // Calculate new position currentX = e.clientX - initialX; currentY = e.clientY - initialY; // Constrain to window bounds const maxX = window.innerWidth - this.container.offsetWidth; const maxY = window.innerHeight - this.container.offsetHeight; currentX = Math.max(0, Math.min(currentX, maxX)); currentY = Math.max(0, Math.min(currentY, maxY)); this.container.style.left = `${currentX}px`; this.container.style.top = `${currentY}px`; }); document.addEventListener('mouseup', () => { if (isDragging) { isDragging = false; this.savePosition(); } }); } createContent() { // Header (outside of content div) const header = this.createHeader(); this.container.appendChild(header); // Main content const content = document.createElement('div'); content.id = 'control-panel-content'; content.style.display = 'block'; // Ensure initial state is visible // Groups Object.entries(this.groupManager.groups).forEach(([groupName, cameras]) => { const groupElement = this.createGroupElement(groupName, cameras); content.appendChild(groupElement); }); // Control buttons const controls = this.createControls(); content.appendChild(controls); this.container.appendChild(content); } createHeader() { const header = document.createElement('div'); header.id = 'control-panel-header'; header.style.cssText = ` display: flex; justify-content: space-between; align-items: center; padding: 5px 10px; border-bottom: 1px solid #e0e0e0; background: #f5f5f5; border-radius: 6px 6px 0 0; `; const title = document.createElement('div'); title.textContent = 'Wyze Camera Controls'; title.style.cssText = ` font-weight: bold; font-size: 16px; color: #4a90e2; `; const minimizeBtn = document.createElement('button'); minimizeBtn.textContent = '−'; minimizeBtn.style.cssText = ` background: none; border: none; font-size: 20px; cursor: pointer; color: #4a90e2; padding: 0 5px; `; minimizeBtn.onclick = () => this.toggleMinimize(); header.appendChild(title); header.appendChild(minimizeBtn); return header; } createGroupElement(groupName, cameras) { const container = document.createElement('div'); container.style.marginBottom = '10px'; const groupButton = document.createElement('button'); groupButton.textContent = groupName; groupButton.style.cssText = ` background: #4a90e2; color: white; border: none; padding: 8px 15px; border-radius: 4px; cursor: pointer; margin-right: 5px; flex: 1; `; const editButton = document.createElement('button'); editButton.textContent = 'Edit'; editButton.style.cssText = ` background: #f5a623; color: white; border: none; padding: 8px 15px; border-radius: 4px; cursor: pointer; `; const deleteButton = document.createElement('button'); deleteButton.textContent = '×'; deleteButton.style.cssText = ` background: #d0021b; color: white; border: none; padding: 8px 12px; border-radius: 4px; cursor: pointer; margin-left: 5px; `; const buttonContainer = document.createElement('div'); buttonContainer.style.display = 'flex'; buttonContainer.appendChild(groupButton); buttonContainer.appendChild(editButton); buttonContainer.appendChild(deleteButton); groupButton.onclick = () => this.selectGroup(cameras); editButton.onclick = () => this.editGroup(groupName); deleteButton.onclick = () => this.deleteGroup(groupName); container.appendChild(buttonContainer); return container; } createControls() { const container = document.createElement('div'); container.style.marginTop = '15px'; const addGroupBtn = document.createElement('button'); addGroupBtn.textContent = '+ Add Group'; addGroupBtn.style.cssText = ` background: #7ed321; color: white; border: none; padding: 8px 15px; border-radius: 4px; cursor: pointer; margin-right: 10px; `; const togglePersonBtn = document.createElement('button'); togglePersonBtn.textContent = 'Toggle Person Detection'; togglePersonBtn.style.cssText = ` background: #9013fe; color: white; border: none; padding: 8px 15px; border-radius: 4px; cursor: pointer; `; addGroupBtn.onclick = () => this.addGroup(); togglePersonBtn.onclick = () => this.togglePersonDetection(); container.appendChild(addGroupBtn); container.appendChild(togglePersonBtn); return container; } toggleMinimize(saveState = true) { const content = this.container.querySelector('#control-panel-content'); const header = this.container.querySelector('#control-panel-header'); const minimizeBtn = header.querySelector('button'); const isMinimized = content.style.display === 'none'; if (isMinimized) { content.style.display = 'block'; minimizeBtn.textContent = '−'; this.container.style.padding = '15px'; } else { content.style.display = 'none'; minimizeBtn.textContent = '+'; this.container.style.padding = '0'; } // Maintain the header's border radius header.style.borderRadius = isMinimized ? '6px' : '6px 6px 0 0'; // Save minimized state if requested if (saveState) { this.groupManager.saveUIState({ minimized: !isMinimized }); } } async selectGroup(cameras) { for (const camera of cameras) { const checkbox = document.querySelector(`input[name="${camera}"]`); if (checkbox) { const parent = checkbox.closest('.MuiButtonBase-root'); if (parent) { parent.click(); await new Promise(resolve => setTimeout(resolve, 50)); } } } } addGroup() { const name = prompt('Enter a name for the new group:'); if (!name) return; const selectedCameras = Array.from(document.querySelectorAll('input[type="checkbox"]:checked')) .map(cb => cb.name); if (selectedCameras.length === 0) { alert('Please select at least one camera.'); return; } try { this.groupManager.addGroup(name, selectedCameras); this.createPanel(); } catch (error) { console.error('Error adding group:', error); alert('Failed to add group. Please try again.'); } } editGroup(groupName) { const newName = prompt('Edit group name:', groupName); if (!newName) return; const selectedCameras = Array.from(document.querySelectorAll('input[type="checkbox"]:checked')) .map(cb => cb.name); if (selectedCameras.length === 0) { alert('Please select at least one camera.'); return; } try { this.groupManager.updateGroup(groupName, newName, selectedCameras); this.createPanel(); } catch (error) { console.error('Error updating group:', error); alert('Failed to update group. Please try again.'); } } deleteGroup(groupName) { if (confirm(`Are you sure you want to delete "${groupName}"?`)) { try { this.groupManager.deleteGroup(groupName); this.createPanel(); } catch (error) { console.error('Error deleting group:', error); alert('Failed to delete group. Please try again.'); } } } togglePersonDetection() { const personButton = Array.from(document.querySelectorAll('button')) .find(btn => btn.innerText.trim().toLowerCase() === 'person'); if (personButton) { personButton.click(); } else { alert('Person Detection button not found. Please check if the UI has changed.'); } } } // Custom CSS for improved video duration text function enhanceVideoText() { const style = document.createElement('style'); style.textContent = ` .css-1a78lvj { font-size: 18px !important; font-weight: bold !important; color: #4a90e2 !important; } `; document.head.appendChild(style); } // Initialize the application function initApp() { const groupManager = new GroupManager(); new ControlPanel(groupManager); enhanceVideoText(); } // Wait for page load if (document.readyState === 'loading') { window.addEventListener('load', initApp); } else { initApp(); } })();