Script for online golf booking in MiScore app

Logs in and redirects directly to a specific booking event when pressing Alt + 8.

// ==UserScript==
    // @name         Script for online golf booking in MiScore app
    // @namespace    http://tampermonkey.net/
    // @version      5.4
    // @license MIT
    // @description  Logs in and redirects directly to a specific booking event when pressing Alt + 8.
    // @author       Paweł Stefaniuk - https://Stefaniuk.site
    // @run-at       document-idle
    // @match        *://*/*
    // @match        *://royalqueensland.miclub.com.au/security/login.msp*
    // @match        *://royalqueensland.miclub.com.au/cms/*
    // @match        *://royalqueensland.miclub.com.au/members/bookings/open/event.msp*
    // @grant        none
    // ==/UserScript==

    (function () {
        'use strict';

     const TARGET_EVENT_ID = "1535259826";
      // <-- YOUR EVENT ID HERE///
     const startTime = "15:00";            // Booking start time (e.g., 14:00)
      const endTime = "15:30";              // Booking end time (e.g., 16:30)


        const targetHour = 6;      // Hour to trigger (0–23)
        const targetMinute = 15;    // Minute to trigger (0–59)
        const targetSecond = 0;    // Second to trigger (0–59)

        const CMS_URL = "https://royalqueensland.miclub.com.au/cms/";
        const LOGIN_URL = "https://royalqueensland.miclub.com.au/security/login.msp";
        const BOOKINGS_EVENT_URL = `https://royalqueensland.miclub.com.au/members/bookings/open/event.msp?booking_event_id=${TARGET_EVENT_ID}&booking_resource_id=3000000`;
        const LOGIN_FLAG = "autoLoginTriggered";

        // Trigger on Alt + 8
        document.addEventListener("keydown", function(event) {
            if (event.altKey && event.key === "8") {
                console.log("🧲 Alt + 8 pressed — initiating action!");
                  localStorage.setItem(LOGIN_FLAG, "true");

                    const eventListUrl = `https://royalqueensland.miclub.com.au/views/members/booking/eventList.xhtml`;
                    window.location.href = eventListUrl;
            }
        });

        // Time-based trigger
        const startCheckingTime = () => {
            const intervalId = setInterval(() => {
                const now = new Date();
                const h = now.getHours();
                const m = now.getMinutes();
                const s = now.getSeconds();

                if (h === targetHour && m === targetMinute && s === targetSecond) {
                    clearInterval(intervalId);
                    localStorage.setItem(LOGIN_FLAG, "true");

                    const eventListUrl = `https://royalqueensland.miclub.com.au/views/members/booking/eventList.xhtml`;
                    window.location.href = eventListUrl;
                }
            }, 1000);
        };

        // Auto login logic
        if (window.location.href.includes("/security/login.msp")) {
            console.log("🔐 Running tryToLogin");
            tryToLogin(0);
        }

        if (window.location.href.includes("eventList.xhtml")) {

            const interval = setInterval(() => {
                const links = Array.from(document.querySelectorAll('a[href*="booking_event_id="]'));
                const openLink = links.find(link =>
                                            link.href.includes(TARGET_EVENT_ID) && link.innerText.includes("OPEN")
                                           );

                if (openLink) {
                    console.log("✅ Event is now OPEN! Navigating to booking page...");
                    clearInterval(interval);
                    localStorage.setItem("autoLoginTriggered", "true");
                    window.location.href = openLink.href + "&autologin=true";
                } else {
                    console.log("🔒 Still locked. Waiting...");
                }
            }, 1000);
        }

        // Redirect after CMS login
        if (window.location.href.startsWith(CMS_URL)) {
            const urlParams = new URLSearchParams(window.location.search);
            const shouldBook = urlParams.get("autologin") === "true";
            if (shouldBook) {
                window.addEventListener("load", () => {
                    console.log("📥 After CMS login – redirecting to booking event");
                    window.location.href = BOOKINGS_EVENT_URL;
                });
            }
        }

        // On event page, attempt booking if autologin is flagged
        if (window.location.href.startsWith(BOOKINGS_EVENT_URL)) {
            console.log("🧪 Entered BOOKINGS_EVENT_URL, flag =", localStorage.getItem(LOGIN_FLAG));
            const urlParams = new URLSearchParams(window.location.search);
            const shouldBook = urlParams.get("autologin") === "true";
            console.log("📥 Changing flag to " + shouldBook);
            if (shouldBook) {
                console.log("✅ Matching URL and flag active — starting booking attempt");
                waitForBookingToOpen();
                localStorage.removeItem(LOGIN_FLAG);
            }
        }
        function waitForBookingToOpen(maxWaitSeconds = 20) {
            console.log("⏳ Waiting for booking to open...");

            let elapsed = 0;
            const interval = setInterval(() => {
                const isOpen = document.querySelector('button.btn-book-me');

                if (isOpen) {
                    console.log("✅ Booking is now OPEN!");
                    clearInterval(interval);

                    const result = findMatchingTimeBlocks(startTime, endTime);
                    const fullyAvailable = filterFullyAvailableSlots(result);
                    clickNextGroupButton(fullyAvailable);
                    localStorage.removeItem(LOGIN_FLAG);

                } else if (elapsed >= maxWaitSeconds) {
                    clearInterval(interval);
                    console.warn("⛔ Timed out waiting for booking to open.");
                        localStorage.removeItem(LOGIN_FLAG); // <--- dodaj to
                } else {
                    elapsed++;
                }
            }, 1000);
        }

        function tryToLogin(attempt) {
            const MAX_ATTEMPTS = 10;
            console.log(`🔍 [Attempt ${attempt + 1}] Looking for login fields...`);

            const actionInput = document.querySelector("input[name='action']");
            console.log("🔎 Looking for hidden 'action' field:", actionInput);

            const loginForm = document.querySelector("form[name='form']");
            console.log("🔎 Looking for login form:", loginForm);

            if (loginForm && actionInput) {
                console.log("✅ All fields found. Submitting form...");
                actionInput.value = "login";
                console.log("✍️ Set hidden action field to 'login'");
                loginForm.submit();
                console.log("🚀 Form submitted");
            } else if (attempt < MAX_ATTEMPTS) {
                console.warn(`⚠️ Fields not ready (attempt ${attempt + 1}). Retrying in 1 second...`);
                setTimeout(() => tryToLogin(attempt + 1), 1000);
            } else {
                console.error("❌ Failed to find login fields after 10 attempts.");
            }
        }

        function findGroupsWithFourSlots() {
            console.log("📥 Looking for groups with 4 available 'Book Me' slots");

            const validZeroCells = Array.from(document.querySelectorAll('div[id$="_0"]')).filter(div =>
                div.querySelector('button.btn-book-me')
            );

            const candidateIds = validZeroCells.map(div => div.id.slice(0, -2));
            const uniqueIds = [...new Set(candidateIds)];

            const validGroupIds = uniqueIds.filter(id =>
                document.getElementById(id + "_0")?.querySelector("button.btn-book-me") &&
                document.getElementById(id + "_1")?.querySelector("button.btn-book-me") &&
                document.getElementById(id + "_2")?.querySelector("button.btn-book-me") &&
                document.getElementById(id + "_3")?.querySelector("button.btn-book-me")
            );

            validGroupIds.sort((a, b) => parseInt(a) - parseInt(b));

            console.log("✅ Groups with 4 available slots:");
            validGroupIds.forEach((id, i) => console.log(`${i + 1}. ID: ${id}`));

            return validGroupIds;
        }

        function timeToMinutes12hFormat(timeStr) {
            const [time, modifier] = timeStr.trim().split(' ');
            let [hours, minutes] = time.split(':').map(Number);
            if (modifier.toLowerCase() === 'pm' && hours !== 12) hours += 12;
            if (modifier.toLowerCase() === 'am' && hours === 12) hours = 0;
            return hours * 60 + minutes;
        }

        function timeToMinutes24hFormat(timeStr) {
            const [h, m] = timeStr.split(':').map(Number);
            return h * 60 + m;
        }

        function findMatchingTimeBlocks(start, end) {
            const startMinutes = timeToMinutes24hFormat(start);
            const endMinutes = timeToMinutes24hFormat(end);

            const matchingIds = [];

            document.querySelectorAll("div.row-heading").forEach(el => {
                const timeEl = el.querySelector("h3");
                if (timeEl) {
                    const blockMinutes = timeToMinutes12hFormat(timeEl.textContent);
                    if (blockMinutes >= startMinutes && blockMinutes <= endMinutes) {
                        matchingIds.push(el.id);
                    }
                }
            });

            return matchingIds;
        }

        function filterFullyAvailableSlots(idList) {
            return idList.filter(headingId => {
                const rowId = headingId.replace("heading-", "");
                const bookingCells = document.querySelectorAll(`div[data-rowid="${rowId}"] .btn-book-me`);
                return bookingCells.length === 4;
            });
        }

        window._originalAlert = window.alert;
        window.alert = function(msg) {
            console.log("🚨 Intercepted alert:", msg);
            if (msg.includes("Booking Row is locked by another user")) {
                console.log("⚠️ Row locked by another user — skipping...");
                setTimeout(() => clickNextGroupButton(window._remainingRowIds || []), 500);
            } else {
                window._originalAlert(msg);
            }
        };

        function clickNextGroupButton(sortedRowIds) {
            if (sortedRowIds.length === 0) {
                console.log("❌ No more booking options available.");
                return;
            }

            sortedRowIds = sortedRowIds
                .map(id => id.toString().replace(/\D/g, ''))
                .map(Number)
                .filter(n => !isNaN(n));

            window._remainingRowIds = sortedRowIds.slice();

            const currentId = sortedRowIds.shift();
            const btn = document.getElementById(`btn-book-group-${currentId}`);

            if (!btn) {
                console.log(`⚠️ Button not found for ID: ${currentId}, trying next...`);
                clickNextGroupButton(sortedRowIds);
                return;
            }

            console.log(`🟡 Clicking BOOK GROUP for ID: ${currentId}`);
            btn.click();

            setTimeout(() => {
                const modal = document.querySelector(".modal-title");
                if (modal && modal.textContent.includes("Would You Like To Book Your Playing Partners?")) {
                    console.log(`✅ Success! Modal appeared for ID: ${currentId}`);
                    clickYesInModal();
                } else {
                    console.log(`🔁 Modal not detected for ID: ${currentId}, trying next`);
                    clickNextGroupButton(sortedRowIds);
                }
            }, 1000);
        }

        function clickYesInModal() {
            const yesButton = Array.from(document.querySelectorAll('button'))
                .find(btn => btn.textContent.trim() === "Yes" && btn.onclick?.toString().includes("submitAutoBook"));

            if (yesButton) {
                console.log("🟢 Clicking 'Yes' in modal...");
                yesButton.click();
            } else {
                console.log("🔍 'Yes' button not yet available – retrying...");
                setTimeout(clickYesInModal, 1000);
            }
        }

        // Start time checking loop
        startCheckingTime();

    })();