SSH XL Auto Responder & Urgent Room Opener

Automatically respond to SSH XL rooms and quickly open the 7 most urgent listings. Speeds up your student housing search!

// ==UserScript==
// @name         SSH XL Auto Responder & Urgent Room Opener
// @namespace    GreenMan36
// @version      1.0
// @description  Automatically respond to SSH XL rooms and quickly open the 7 most urgent listings. Speeds up your student housing search!
// @author       GreenMan36
// @match        https://www.sshxl.nl/nl/aanbod*
// @grant        none
// @license      MIT
// @homepage     https://github.com/GreenMan36
// @icon         https://www.sshxl.nl/favicon.ico
// ==/UserScript==

(function() {
    'use strict';

    console.log('SSH XL: Script starting...');
    console.log('SSH XL: Current URL is:', window.location.href);
    console.log('SSH XL: Current pathname is:', window.location.pathname);

    // AUTO-RESPONSE FUNCTIONALITY FOR INDIVIDUAL ROOM PAGES
    function isRoomPage() {
        const pathname = window.location.pathname;
        const aanbodMatch = pathname.match(/\/aanbod\/(.+)/);
        const hasRoomId = aanbodMatch && aanbodMatch[1] && aanbodMatch[1].trim() !== '';

        return hasRoomId;
    }

    // Wait for rooms to load on main page
    function waitForCards() {
        const cards = document.querySelectorAll('.card--property');

        if (cards.length > 0) {
            addUrgentOpenerButton();
        } else {
            setTimeout(waitForCards, 1000);
        }
    }

    // Add a floating button to trigger the urgent room opener
    function addUrgentOpenerButton() {
        // Remove existing button if present
        const existingButton = document.getElementById('ssh-urgent-opener');
        if (existingButton) existingButton.remove();

        const button = document.createElement('button');
        button.id = 'ssh-urgent-opener';
        button.innerHTML = '🏠 Open 7 Most Urgent';
        button.style.cssText = `
            position: fixed;
            bottom: 120px;
            right: 20px;
            background: #dc3545;
            color: white;
            border: none;
            padding: 15px 20px;
            border-radius: 25px;
            font-weight: bold;
            font-size: 14px;
            cursor: pointer;
            z-index: 9999;
            box-shadow: 0 4px 12px rgba(0,0,0,0.3);
            transition: all 0.3s ease;
        `;

        button.onmouseover = () => {
            button.style.background = '#c82333';
            button.style.transform = 'scale(1.05)';
        };
        button.onmouseout = () => {
            button.style.background = '#dc3545';
            button.style.transform = 'scale(1)';
        };

        button.onclick = openUrgentRoomsInteractive;

        document.body.appendChild(button);
    }

    function openUrgentRooms() {
        const cards = document.querySelectorAll('.card--property');

        if (cards.length === 0) {
            console.log('No property cards found on this page');
            return [];
        }

        console.log(`Found ${cards.length} total rooms`);

        // Take the last 7 cards (most urgent according to page ordering)
        const urgentCards = Array.from(cards).slice(-7);

        console.log(`Taking the last ${urgentCards.length} rooms (most urgent):`);

        // Extract data and open tabs
        const urgentRooms = urgentCards.map((card, index) => {
            const link = card.querySelector('.card__link');
            const timeElement = card.querySelector('.card__footer p');
            const title = card.querySelector('.card__title')?.textContent.trim() || 'Unknown';
            const price = card.querySelector('.price-tag')?.textContent.trim() || 'Unknown';

            if (!link || !timeElement) {
                console.log(`Card ${index}: Missing link or time element`);
                return null;
            }

            const href = link.getAttribute('href');
            const timeText = timeElement.textContent.trim();
            const fullUrl = window.location.origin + href;

            console.log(`${index + 1}. ${title} - ${price} - ${timeText}`);

            // Open in new tab with small delay
            setTimeout(() => {
                window.open(fullUrl, '_blank');
            }, index * 300);

            return {
                href: href,
                timeText: timeText,
                title: title,
                price: price
            };
        }).filter(room => room !== null);

        return urgentRooms;
    }

    function createRoomOverlay(urgentRooms) {
        // Remove existing overlay if present
        const existingOverlay = document.getElementById('urgent-rooms-overlay');
        if (existingOverlay) existingOverlay.remove();

        // Create overlay
        const overlay = document.createElement('div');
        overlay.id = 'urgent-rooms-overlay';
        overlay.style.cssText = `
            position: fixed;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            background: rgba(0,0,0,0.8);
            z-index: 10000;
            display: flex;
            align-items: center;
            justify-content: center;
            font-family: Arial, sans-serif;
        `;

        const content = document.createElement('div');
        content.style.cssText = `
            background: white;
            border-radius: 10px;
            padding: 20px;
            max-width: 600px;
            max-height: 80vh;
            overflow-y: auto;
            box-shadow: 0 4px 20px rgba(0,0,0,0.3);
        `;

        content.innerHTML = `
            <h2 style="margin-top: 0; color: #333;">🏠 7 Most Urgent Rooms</h2>
            <p style="color: #666; margin-bottom: 20px;">Click to open each room in a new tab:</p>
            <div id="room-buttons"></div>
            <button id="close-overlay" style="
                background: #dc3545;
                color: white;
                border: none;
                padding: 10px 20px;
                border-radius: 5px;
                cursor: pointer;
                margin-top: 20px;
                float: right;
            ">Close</button>
            <div style="clear: both;"></div>
        `;

        const buttonContainer = content.querySelector('#room-buttons');

        // Add button for each room
        urgentRooms.forEach((room, index) => {
            const button = document.createElement('button');
            button.style.cssText = `
                display: block;
                width: 100%;
                text-align: left;
                background: #f8f9fa;
                border: 1px solid #dee2e6;
                padding: 15px;
                margin-bottom: 10px;
                border-radius: 5px;
                cursor: pointer;
                transition: background-color 0.2s;
            `;

            button.innerHTML = `
                <strong>${index + 1}. ${room.title}</strong><br>
                <span style="color: #28a745; font-weight: bold;">${room.price}</span><br>
                <span style="color: #dc3545; font-size: 0.9em;">${room.timeText}</span>
            `;

            button.onmouseover = () => button.style.backgroundColor = '#e9ecef';
            button.onmouseout = () => button.style.backgroundColor = '#f8f9fa';

            button.onclick = () => {
                const fullUrl = window.location.origin + room.href;
                window.open(fullUrl, '_blank');
                button.style.backgroundColor = '#d4edda';
                button.innerHTML += '<br><span style="color: #155724; font-size: 0.8em;">✓ Opened</span>';
            };

            buttonContainer.appendChild(button);
        });

        content.querySelector('#close-overlay').onclick = () => overlay.remove();
        overlay.onclick = (e) => {
            if (e.target === overlay) overlay.remove();
        };

        overlay.appendChild(content);
        document.body.appendChild(overlay);
    }

    function openUrgentRoomsInteractive() {
        const urgentRooms = openUrgentRooms();

        if (urgentRooms.length === 0) {
            alert('No rooms found! Make sure the page has loaded completely.');
            return;
        }

        console.log('\n🚨 If tabs didn\'t open automatically, use the overlay to click each room manually, this might be a bug.');

        createRoomOverlay(urgentRooms);

        const button = document.getElementById('ssh-urgent-opener');
        if (button) {
            const originalText = button.innerHTML;
            button.innerHTML = '✅ Opened!';
            button.style.background = '#28a745';
            setTimeout(() => {
                button.innerHTML = originalText;
                button.style.background = '#dc3545';
            }, 3000);
        }
    }

    function observePageChanges() {
        const observer = new MutationObserver((mutations) => {
            mutations.forEach((mutation) => {
                if (mutation.type === 'childList') {
                    const cards = document.querySelectorAll('.card--property');
                    if (cards.length > 0 && !document.getElementById('ssh-urgent-opener')) {
                        console.log('Changes detected, re-adding urgent room button.');
                        addUrgentOpenerButton();
                    }
                }
            });
        });

        observer.observe(document.body, {
            childList: true,
            subtree: true
        });
    }

    // for the room page
    function waitForReactButton() {
        return new Promise((resolve, reject) => {
            const maxWaitTime = 30000; // 30 seconds max wait
            const startTime = Date.now();

            function checkForButton() {
                console.log('SSH XL: Checking for "Reageer op deze kamer/woning" button...');

                // Try multiple selectors, idk which one worked in the end and am too lazy
                const selectors = [
                    '.pageheader__actions button',
                    'button[class*="button"]',
                    'button'
                ];

                let reactButton = null;
                let allButtons = [];

                for (const selector of selectors) {
                    const buttons = document.querySelectorAll(selector);
                    console.log(`SSH XL: Found ${buttons.length} buttons with selector "${selector}"`);

                    buttons.forEach((btn, index) => {
                        const text = btn.textContent || btn.innerText || '';
                        console.log(`SSH XL: Button ${index}: "${text.trim()}"`);
                        allButtons.push({selector, index, text: text.trim()});

                        if (text.includes('Reageer op deze kamer') || text.includes('Reageer op deze woning')) {
                            console.log('SSH XL: Found "Reageer op deze kamer/woning" button!');
                            reactButton = btn;
                        }
                    });
                }

                if (reactButton) {
                    resolve(reactButton);
                    return;
                }

                console.log('SSH XL: All buttons found:', allButtons);

                if (Date.now() - startTime > maxWaitTime) {
                    console.log('Timeout waiting for "Reageer op deze kamer/woning" button');
                    reject(new Error('Timeout waiting for react button'));
                    return;
                }

                setTimeout(checkForButton, 2000);
            }

            checkForButton();

          // might be redundant but idc, gotta refactor whenever but it works so who cares
            const observer = new MutationObserver((mutations) => {
                console.log('DOM mutation detected, checking for button...');
                const reactButton = document.querySelector('.pageheader__actions button');
                if (reactButton && (reactButton.textContent.includes('Reageer op deze kamer') || reactButton.textContent.includes('Reageer op deze woning'))) {
                    console.log('Found "Reageer op deze kamer/woning" button via observer!');
                    observer.disconnect();
                    resolve(reactButton);
                }
            });

            observer.observe(document.body, {
                childList: true,
                subtree: true
            });

            // Clean up observer on timeout
            setTimeout(() => {
                observer.disconnect();
            }, maxWaitTime);
        });
    }

    function waitForConfirmButton() {
        return new Promise((resolve, reject) => {
            const maxWaitTime = 10000;
            const startTime = Date.now();

            function checkForConfirmButton() {
                const confirmButton = document.querySelector('.modal__footer .button--primary');

                if (confirmButton && confirmButton.textContent.includes('Bevestigen')) {
                    resolve(confirmButton);
                    return;
                }

                if (Date.now() - startTime > maxWaitTime) {
                    reject(new Error('Timeout waiting for confirm button'));
                    return;
                }

                setTimeout(checkForConfirmButton, 200);
            }

            checkForConfirmButton();

          // might be redundant but idc, gotta refactor whenever but it works so who cares
            const observer = new MutationObserver((mutations) => {
                mutations.forEach((mutation) => {
                    if (mutation.type === 'childList') {
                        const confirmButton = document.querySelector('.modal__footer .button--primary');
                        if (confirmButton && confirmButton.textContent.includes('Bevestigen')) {
                            console.log('SSH XL: Found "Bevestigen" button via observer!');
                            observer.disconnect();
                            resolve(confirmButton);
                        }
                    }
                });
            });

            observer.observe(document.body, {
                childList: true,
                subtree: true
            });

            setTimeout(() => {
                observer.disconnect();
            }, maxWaitTime);
        });
    }

    async function clickReactButton(button) {
        addClickIndicator('Clicking "Reageer" button...', '#ff6b35');

        button.click();

        try {
            const confirmButton = await waitForConfirmButton();
            clickConfirmButton(confirmButton);
        } catch (error) {
            updateClickIndicator('❌ Error: Modal not found', '#dc3545');
        }
    }

    function clickConfirmButton(button) {
        // Update visual indicator
        updateClickIndicator('Clicking "Bevestigen"...', '#28a745');

        button.click();

        // Show success message
        setTimeout(() => {
            updateClickIndicator('✅ Response submitted!', '#28a745');
            setTimeout(() => {
                removeClickIndicator();
            }, 3000);
        }, 1000);
    }

    function addClickIndicator(message, color) {
        // Remove existing indicator
        removeClickIndicator();

        const indicator = document.createElement('div');
        indicator.id = 'ssh-click-indicator';
        indicator.innerHTML = message;
        indicator.style.cssText = `
            position: fixed;
            top: 20px;
            right: 20px;
            background: ${color};
            color: white;
            padding: 15px 20px;
            border-radius: 10px;
            font-weight: bold;
            font-size: 14px;
            z-index: 10000;
            box-shadow: 0 4px 12px rgba(0,0,0,0.3);
            animation: slideIn 0.3s ease;
        `;

        // Add animation
        const style = document.createElement('style');
        style.textContent = `
            @keyframes slideIn {
                from { transform: translateX(100%); opacity: 0; }
                to { transform: translateX(0); opacity: 1; }
            }
        `;
        document.head.appendChild(style);

        document.body.appendChild(indicator);
    }

    function updateClickIndicator(message, color) {
        const indicator = document.getElementById('ssh-click-indicator');
        if (indicator) {
            indicator.innerHTML = message;
            indicator.style.background = color;
        }
    }

    function removeClickIndicator() {
        const indicator = document.getElementById('ssh-click-indicator');
        if (indicator) indicator.remove();
    }

    // Initialize based on page type
    const roomPageCheck = isRoomPage();
    console.log('=== PAGE DETECTION COMPLETE ===');
    if (roomPageCheck) {
        console.log('❌ DETECTED ROOM PAGE - waiting for react button...');
        async function startAutoResponse() {
            try {
                addClickIndicator('Waiting for react button to load...', '#6c757d');
                const reactButton = await waitForReactButton();
                console.log('✅ REACT BUTTOn LOADED - applying for room now...');
                clickReactButton(reactButton);
            } catch (error) {
                updateClickIndicator('❌ Page load timeout', '#dc3545');
                setTimeout(removeClickIndicator, 5000);
            }
        }

        startAutoResponse();

    } else {
        console.log('❌ DETECTED LISTING PAGE - adding urgent opener...');

        // Initialize when page loads
        if (document.readyState === 'loading') {
            document.addEventListener('DOMContentLoaded', () => {
                console.log('✅ LISTING PAGE LOADED - waiting a bit before activating...');
                setTimeout(waitForCards, 2000); // Give extra time for content
                observePageChanges();
            });
        } else {
            console.log('✅ LISTING PAGE AlREADY LOADED - Activating...');
            setTimeout(waitForCards, 2000);
            observePageChanges();
        }
    }

})();