Simple Calendly Tennis Monitor

Monitor Calendly tennis class availability

// ==UserScript==
// @name         Simple Calendly Tennis Monitor
// @namespace    http://tampermonkey.net/
// @version      1.1
// @description  Monitor Calendly tennis class availability
// @match        https://calendly.com/santitennis*
// @grant        GM_notification
// @run-at       document-idle
// @license MIT
// ==/UserScript==

(function() {
    'use strict';

    let monitorInterval = null;
    const CHECK_INTERVAL = 60000; // Check every 60 seconds
    const AUDIO_URL = 'https://cdn.pixabay.com/download/audio/2022/03/15/audio_99b282eb1c.mp3?filename=notification-sound-7062.mp3';

    // Simple UI
    const COLORS = {
        primary: '#1a365d',
        success: '#22c55e',
    };

    // Toast notifications
    class ToastSystem {
        constructor() {
            this.container = document.createElement('div');
            this.container.style.cssText = `
                position: fixed;
                bottom: 20px;
                right: 20px;
                z-index: 10000;
            `;
            document.body.appendChild(this.container);
        }

        show(message, type = 'info', duration = 3000) {
            const toast = document.createElement('div');
            toast.style.cssText = `
                background: ${type === 'success' ? COLORS.success : COLORS.primary};
                color: white;
                padding: 12px 24px;
                border-radius: 8px;
                box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
                margin-top: 8px;
                font-family: -apple-system, system-ui, sans-serif;
                font-size: 14px;
            `;
            toast.textContent = message;
            this.container.appendChild(toast);

            if (duration > 0) {
                setTimeout(() => toast.remove(), duration);
            }
            return toast;
        }
    }

    function findAvailableTimeSlot() {
        const buttons = document.querySelectorAll('button');
        return Array.from(buttons).find(button =>
            !button.disabled && button.getAttribute('aria-label')?.includes('Horas disponibles')
        );
    }

    function playNotificationSound() {
        const audio = new Audio(AUDIO_URL);
        audio.play();
    }

    function checkAvailability() {
        const availableButton = findAvailableTimeSlot();

        if (availableButton) {
            if (Notification.permission === "granted") {
                new Notification("Tennis Class Available!", {
                    body: "New slots available!",
                    requireInteraction: true
                });
            }
            toastSystem.show('Slots Available!', 'success', 0);
            monitorButton.style.background = COLORS.success;
            playNotificationSound();
        }
    }

    function toggleMonitoring() {
        if (monitorInterval) {
            clearInterval(monitorInterval);
            monitorInterval = null;
            monitorButton.textContent = '▶️';
            toastSystem.show('Monitoring stopped', 'info');
            monitorButton.style.background = COLORS.primary;
        } else {
            if (Notification.permission === "default") {
                Notification.requestPermission();
            }

            checkAvailability();
            monitorInterval = setInterval(checkAvailability, CHECK_INTERVAL);
            monitorButton.textContent = '⏸️';
            toastSystem.show('Monitoring started', 'success');
        }
    }

    // Create minimal UI
    const monitorButton = document.createElement('button');
    monitorButton.textContent = '▶️';
    monitorButton.style.cssText = `
        position: fixed;
        top: 10px;
        right: 10px;
        background: ${COLORS.primary};
        color: white;
        padding: 8px 12px;
        border: none;
        border-radius: 4px;
        cursor: pointer;
        z-index: 10000;
    `;
    monitorButton.onclick = toggleMonitoring;
    document.body.appendChild(monitorButton);

    const toastSystem = new ToastSystem();
})();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址