Simple Calendly Tennis Monitor

Monitor Calendly tennis class availability

  1. // ==UserScript==
  2. // @name Simple Calendly Tennis Monitor
  3. // @namespace http://tampermonkey.net/
  4. // @version 1.1
  5. // @description Monitor Calendly tennis class availability
  6. // @match https://calendly.com/santitennis*
  7. // @grant GM_notification
  8. // @run-at document-idle
  9. // @license MIT
  10. // ==/UserScript==
  11.  
  12. (function() {
  13. 'use strict';
  14.  
  15. let monitorInterval = null;
  16. const CHECK_INTERVAL = 60000; // Check every 60 seconds
  17. const AUDIO_URL = 'https://cdn.pixabay.com/download/audio/2022/03/15/audio_99b282eb1c.mp3?filename=notification-sound-7062.mp3';
  18.  
  19. // Simple UI
  20. const COLORS = {
  21. primary: '#1a365d',
  22. success: '#22c55e',
  23. };
  24.  
  25. // Toast notifications
  26. class ToastSystem {
  27. constructor() {
  28. this.container = document.createElement('div');
  29. this.container.style.cssText = `
  30. position: fixed;
  31. bottom: 20px;
  32. right: 20px;
  33. z-index: 10000;
  34. `;
  35. document.body.appendChild(this.container);
  36. }
  37.  
  38. show(message, type = 'info', duration = 3000) {
  39. const toast = document.createElement('div');
  40. toast.style.cssText = `
  41. background: ${type === 'success' ? COLORS.success : COLORS.primary};
  42. color: white;
  43. padding: 12px 24px;
  44. border-radius: 8px;
  45. box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
  46. margin-top: 8px;
  47. font-family: -apple-system, system-ui, sans-serif;
  48. font-size: 14px;
  49. `;
  50. toast.textContent = message;
  51. this.container.appendChild(toast);
  52.  
  53. if (duration > 0) {
  54. setTimeout(() => toast.remove(), duration);
  55. }
  56. return toast;
  57. }
  58. }
  59.  
  60. function findAvailableTimeSlot() {
  61. const buttons = document.querySelectorAll('button');
  62. return Array.from(buttons).find(button =>
  63. !button.disabled && button.getAttribute('aria-label')?.includes('Horas disponibles')
  64. );
  65. }
  66.  
  67. function playNotificationSound() {
  68. const audio = new Audio(AUDIO_URL);
  69. audio.play();
  70. }
  71.  
  72. function checkAvailability() {
  73. const availableButton = findAvailableTimeSlot();
  74.  
  75. if (availableButton) {
  76. if (Notification.permission === "granted") {
  77. new Notification("Tennis Class Available!", {
  78. body: "New slots available!",
  79. requireInteraction: true
  80. });
  81. }
  82. toastSystem.show('Slots Available!', 'success', 0);
  83. monitorButton.style.background = COLORS.success;
  84. playNotificationSound();
  85. }
  86. }
  87.  
  88. function toggleMonitoring() {
  89. if (monitorInterval) {
  90. clearInterval(monitorInterval);
  91. monitorInterval = null;
  92. monitorButton.textContent = '▶️';
  93. toastSystem.show('Monitoring stopped', 'info');
  94. monitorButton.style.background = COLORS.primary;
  95. } else {
  96. if (Notification.permission === "default") {
  97. Notification.requestPermission();
  98. }
  99.  
  100. checkAvailability();
  101. monitorInterval = setInterval(checkAvailability, CHECK_INTERVAL);
  102. monitorButton.textContent = '⏸️';
  103. toastSystem.show('Monitoring started', 'success');
  104. }
  105. }
  106.  
  107. // Create minimal UI
  108. const monitorButton = document.createElement('button');
  109. monitorButton.textContent = '▶️';
  110. monitorButton.style.cssText = `
  111. position: fixed;
  112. top: 10px;
  113. right: 10px;
  114. background: ${COLORS.primary};
  115. color: white;
  116. padding: 8px 12px;
  117. border: none;
  118. border-radius: 4px;
  119. cursor: pointer;
  120. z-index: 10000;
  121. `;
  122. monitorButton.onclick = toggleMonitoring;
  123. document.body.appendChild(monitorButton);
  124.  
  125. const toastSystem = new ToastSystem();
  126. })();

QingJ © 2025

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