BLS Spain Morocco Auto Appointment Booker

Automatically checks for appointment slots, auto-fills forms, and books appointments on BLS Spain Morocco visa website

Size

25.8 KB

Version

1.1.1

Created

Dec 6, 2025

Updated

6 days ago

1// ==UserScript==
2// @name		BLS Spain Morocco Auto Appointment Booker
3// @description		Automatically checks for appointment slots, auto-fills forms, and books appointments on BLS Spain Morocco visa website
4// @version		1.1.1
5// @match		https://www.blsspainmorocco.net/*
6// @icon		https://robomonkey.io/favicon.ico
7// @grant		GM.getValue
8// @grant		GM.setValue
9// @grant		GM.deleteValue
10// @grant		GM.listValues
11// @grant		GM.notification
12// ==/UserScript==
13(function() {
14    'use strict';
15
16    // Configuration and State Management
17    const CONFIG = {
18        CHECK_INTERVAL: 30000, // Check every 30 seconds
19        AUTO_BOOK_ENABLED: false,
20        NOTIFICATION_SOUND: true,
21        MAX_RETRIES: 3
22    };
23
24    let isChecking = false;
25    let checkInterval = null;
26
27    // Utility Functions
28    function log(message, data = null) {
29        const timestamp = new Date().toLocaleTimeString();
30        console.log(`[BLS Auto Booker ${timestamp}] ${message}`, data || '');
31    }
32
33    function debounce(func, wait) {
34        let timeout;
35        return function executedFunction(...args) {
36            const later = () => {
37                clearTimeout(timeout);
38                func(...args);
39            };
40            clearTimeout(timeout);
41            timeout = setTimeout(later, wait);
42        };
43    }
44
45    // Storage Functions
46    async function saveCredentials(email, password) {
47        try {
48            await GM.setValue('bls_email', email);
49            await GM.setValue('bls_password', password);
50            log('Credentials saved successfully');
51            return true;
52        } catch (error) {
53            console.error('Error saving credentials:', error);
54            return false;
55        }
56    }
57
58    async function getCredentials() {
59        try {
60            const email = await GM.getValue('bls_email', '');
61            const password = await GM.getValue('bls_password', '');
62            return { email, password };
63        } catch (error) {
64            console.error('Error getting credentials:', error);
65            return { email: '', password: '' };
66        }
67    }
68
69    async function saveFormData(data) {
70        try {
71            await GM.setValue('bls_form_data', JSON.stringify(data));
72            log('Form data saved successfully');
73            return true;
74        } catch (error) {
75            console.error('Error saving form data:', error);
76            return false;
77        }
78    }
79
80    async function getFormData() {
81        try {
82            const data = await GM.getValue('bls_form_data', '{}');
83            return JSON.parse(data);
84        } catch (error) {
85            console.error('Error getting form data:', error);
86            return {};
87        }
88    }
89
90    async function getConfig() {
91        try {
92            const autoBook = await GM.getValue('auto_book_enabled', false);
93            const checkInterval = await GM.getValue('check_interval', 30000);
94            const notificationSound = await GM.getValue('notification_sound', true);
95            return { autoBook, checkInterval, notificationSound };
96        } catch (error) {
97            console.error('Error getting config:', error);
98            return CONFIG;
99        }
100    }
101
102    async function saveConfig(config) {
103        try {
104            await GM.setValue('auto_book_enabled', config.autoBook);
105            await GM.setValue('check_interval', config.checkInterval);
106            await GM.setValue('notification_sound', config.notificationSound);
107            log('Config saved successfully');
108            return true;
109        } catch (error) {
110            console.error('Error saving config:', error);
111            return false;
112        }
113    }
114
115    // Auto-fill Login Form
116    async function autoFillLogin() {
117        log('Attempting to auto-fill login form');
118        
119        const emailSelectors = [
120            'input[type="email"]',
121            'input[name*="email" i]',
122            'input[id*="email" i]',
123            'input[placeholder*="email" i]',
124            'input[name*="username" i]',
125            'input[id*="username" i]'
126        ];
127
128        const passwordSelectors = [
129            'input[type="password"]',
130            'input[name*="password" i]',
131            'input[id*="password" i]'
132        ];
133
134        let emailInput = null;
135        let passwordInput = null;
136
137        // Find email input
138        for (const selector of emailSelectors) {
139            emailInput = document.querySelector(selector);
140            if (emailInput) {
141                log('Found email input with selector:', selector);
142                break;
143            }
144        }
145
146        // Find password input
147        for (const selector of passwordSelectors) {
148            passwordInput = document.querySelector(selector);
149            if (passwordInput) {
150                log('Found password input with selector:', selector);
151                break;
152            }
153        }
154
155        if (emailInput && passwordInput) {
156            const credentials = await getCredentials();
157            
158            if (credentials.email && credentials.password) {
159                emailInput.value = credentials.email;
160                passwordInput.value = credentials.password;
161                
162                // Trigger input events
163                emailInput.dispatchEvent(new Event('input', { bubbles: true }));
164                emailInput.dispatchEvent(new Event('change', { bubbles: true }));
165                passwordInput.dispatchEvent(new Event('input', { bubbles: true }));
166                passwordInput.dispatchEvent(new Event('change', { bubbles: true }));
167                
168                log('Login form auto-filled successfully');
169                return true;
170            } else {
171                log('No saved credentials found');
172            }
173        } else {
174            log('Login form inputs not found');
175        }
176        
177        return false;
178    }
179
180    // Check for Available Appointments
181    async function checkAppointmentSlots() {
182        log('Checking for available appointment slots...');
183        
184        const slotSelectors = [
185            'button[class*="available"]',
186            'div[class*="available"]',
187            'td[class*="available"]',
188            'span[class*="available"]',
189            '.appointment-slot:not(.disabled)',
190            '.date-available',
191            '[data-available="true"]'
192        ];
193
194        let availableSlots = [];
195
196        for (const selector of slotSelectors) {
197            const elements = document.querySelectorAll(selector);
198            if (elements.length > 0) {
199                availableSlots = Array.from(elements);
200                log(`Found ${availableSlots.length} available slots with selector: ${selector}`);
201                break;
202            }
203        }
204
205        if (availableSlots.length > 0) {
206            log('Available appointment slots found!', availableSlots.length);
207            await notifyUser('Appointment Available!', `Found ${availableSlots.length} available slot(s)`);
208            
209            const config = await getConfig();
210            if (config.autoBook) {
211                await bookFirstAvailableSlot(availableSlots[0]);
212            }
213            
214            return availableSlots;
215        } else {
216            log('No available slots found');
217            return [];
218        }
219    }
220
221    // Book First Available Slot
222    async function bookFirstAvailableSlot(slotElement) {
223        log('Attempting to book first available slot');
224        
225        try {
226            // Click the slot
227            slotElement.click();
228            log('Clicked on available slot');
229            
230            // Wait for form to appear
231            await new Promise(resolve => setTimeout(resolve, 2000));
232            
233            // Auto-fill the booking form
234            await autoFillBookingForm();
235            
236            // Find and click submit button
237            const submitSelectors = [
238                'button[type="submit"]',
239                'input[type="submit"]',
240                'button[class*="submit"]',
241                'button[class*="book"]',
242                'button[class*="confirm"]'
243            ];
244
245            let submitButton = null;
246            for (const selector of submitSelectors) {
247                submitButton = document.querySelector(selector);
248                if (submitButton) {
249                    log('Found submit button with selector:', selector);
250                    break;
251                }
252            }
253
254            if (submitButton) {
255                submitButton.click();
256                log('Clicked submit button');
257                await notifyUser('Booking Submitted!', 'Appointment booking has been submitted');
258                return true;
259            } else {
260                log('Submit button not found');
261                return false;
262            }
263        } catch (error) {
264            console.error('Error booking slot:', error);
265            return false;
266        }
267    }
268
269    // Auto-fill Booking Form
270    async function autoFillBookingForm() {
271        log('Auto-filling booking form');
272        
273        const formData = await getFormData();
274        
275        if (Object.keys(formData).length === 0) {
276            log('No saved form data found');
277            return false;
278        }
279
280        // Fill text inputs
281        for (const [key, value] of Object.entries(formData)) {
282            const input = document.querySelector(`input[name="${key}"], input[id="${key}"]`);
283            if (input && value) {
284                input.value = value;
285                input.dispatchEvent(new Event('input', { bubbles: true }));
286                input.dispatchEvent(new Event('change', { bubbles: true }));
287                log(`Filled field: ${key}`);
288            }
289        }
290
291        log('Booking form auto-filled');
292        return true;
293    }
294
295    // Send Notification
296    async function notifyUser(title, message) {
297        log(`Notification: ${title} - ${message}`);
298        
299        try {
300            // Browser notification
301            if (typeof GM.notification !== 'undefined') {
302                GM.notification({
303                    title: title,
304                    text: message,
305                    timeout: 10000
306                });
307            }
308            
309            // Visual notification on page
310            showPageNotification(title, message);
311            
312            // Sound notification
313            const config = await getConfig();
314            if (config.notificationSound) {
315                playNotificationSound();
316            }
317        } catch (error) {
318            console.error('Error sending notification:', error);
319        }
320    }
321
322    // Show Page Notification
323    function showPageNotification(title, message) {
324        const notification = document.createElement('div');
325        notification.style.cssText = `
326            position: fixed;
327            top: 20px;
328            right: 20px;
329            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
330            color: white;
331            padding: 20px;
332            border-radius: 10px;
333            box-shadow: 0 10px 30px rgba(0,0,0,0.3);
334            z-index: 999999;
335            max-width: 350px;
336            font-family: Arial, sans-serif;
337            animation: slideIn 0.3s ease-out;
338        `;
339        
340        notification.innerHTML = `
341            <div style="font-size: 18px; font-weight: bold; margin-bottom: 8px;">${title}</div>
342            <div style="font-size: 14px; opacity: 0.9;">${message}</div>
343        `;
344        
345        document.body.appendChild(notification);
346        
347        setTimeout(() => {
348            notification.style.animation = 'slideOut 0.3s ease-in';
349            setTimeout(() => notification.remove(), 300);
350        }, 5000);
351    }
352
353    // Play Notification Sound
354    function playNotificationSound() {
355        try {
356            const audioContext = new (window.AudioContext || window.webkitAudioContext)();
357            const oscillator = audioContext.createOscillator();
358            const gainNode = audioContext.createGain();
359            
360            oscillator.connect(gainNode);
361            gainNode.connect(audioContext.destination);
362            
363            oscillator.frequency.value = 800;
364            oscillator.type = 'sine';
365            
366            gainNode.gain.setValueAtTime(0.3, audioContext.currentTime);
367            gainNode.gain.exponentialRampToValueAtTime(0.01, audioContext.currentTime + 0.5);
368            
369            oscillator.start(audioContext.currentTime);
370            oscillator.stop(audioContext.currentTime + 0.5);
371        } catch (error) {
372            console.error('Error playing sound:', error);
373        }
374    }
375
376    // Create Control Panel UI
377    function createControlPanel() {
378        log('Creating control panel');
379        
380        // Check if panel already exists
381        if (document.getElementById('bls-control-panel')) {
382            return;
383        }
384
385        const panel = document.createElement('div');
386        panel.id = 'bls-control-panel';
387        panel.style.cssText = `
388            position: fixed;
389            bottom: 20px;
390            right: 20px;
391            width: 350px;
392            background: white;
393            border-radius: 12px;
394            box-shadow: 0 10px 40px rgba(0,0,0,0.2);
395            z-index: 999998;
396            font-family: Arial, sans-serif;
397            overflow: hidden;
398        `;
399
400        panel.innerHTML = `
401            <style>
402                @keyframes slideIn {
403                    from { transform: translateX(400px); opacity: 0; }
404                    to { transform: translateX(0); opacity: 1; }
405                }
406                @keyframes slideOut {
407                    from { transform: translateX(0); opacity: 1; }
408                    to { transform: translateX(400px); opacity: 0; }
409                }
410                .bls-btn {
411                    padding: 10px 20px;
412                    border: none;
413                    border-radius: 6px;
414                    cursor: pointer;
415                    font-size: 14px;
416                    font-weight: 600;
417                    transition: all 0.3s;
418                    width: 100%;
419                    margin-top: 8px;
420                }
421                .bls-btn-primary {
422                    background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
423                    color: white;
424                }
425                .bls-btn-primary:hover {
426                    transform: translateY(-2px);
427                    box-shadow: 0 5px 15px rgba(102, 126, 234, 0.4);
428                }
429                .bls-btn-secondary {
430                    background: #f0f0f0;
431                    color: #333;
432                }
433                .bls-btn-secondary:hover {
434                    background: #e0e0e0;
435                }
436                .bls-input {
437                    width: 100%;
438                    padding: 10px;
439                    border: 2px solid #e0e0e0;
440                    border-radius: 6px;
441                    font-size: 14px;
442                    margin-top: 8px;
443                    box-sizing: border-box;
444                }
445                .bls-input:focus {
446                    outline: none;
447                    border-color: #667eea;
448                }
449                .bls-checkbox {
450                    margin-right: 8px;
451                }
452                .bls-status {
453                    display: inline-block;
454                    width: 10px;
455                    height: 10px;
456                    border-radius: 50%;
457                    margin-right: 8px;
458                }
459                .bls-status-active {
460                    background: #4caf50;
461                    box-shadow: 0 0 10px #4caf50;
462                }
463                .bls-status-inactive {
464                    background: #ccc;
465                }
466            </style>
467            <div style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; padding: 15px;">
468                <div style="display: flex; justify-content: space-between; align-items: center;">
469                    <h3 style="margin: 0; font-size: 16px;">BLS Auto Booker</h3>
470                    <button id="bls-toggle-panel" style="background: rgba(255,255,255,0.2); border: none; color: white; padding: 5px 10px; border-radius: 4px; cursor: pointer;"></button>
471                </div>
472            </div>
473            <div id="bls-panel-content" style="padding: 15px;">
474                <div style="margin-bottom: 15px;">
475                    <div style="display: flex; align-items: center; margin-bottom: 10px;">
476                        <span class="bls-status bls-status-inactive" id="bls-status-indicator"></span>
477                        <span style="font-size: 14px; color: #666;" id="bls-status-text">Monitoring: Inactive</span>
478                    </div>
479                </div>
480
481                <div style="margin-bottom: 15px;">
482                    <label style="display: block; font-size: 14px; font-weight: 600; color: #333; margin-bottom: 5px;">Login Credentials</label>
483                    <input type="email" id="bls-email" class="bls-input" placeholder="Email/Username">
484                    <input type="password" id="bls-password" class="bls-input" placeholder="Password">
485                    <button id="bls-save-credentials" class="bls-btn bls-btn-secondary">Save Credentials</button>
486                </div>
487
488                <div style="margin-bottom: 15px;">
489                    <label style="display: flex; align-items: center; font-size: 14px; color: #333; cursor: pointer;">
490                        <input type="checkbox" id="bls-auto-book" class="bls-checkbox">
491                        <span>Enable Auto-Booking</span>
492                    </label>
493                    <label style="display: flex; align-items: center; font-size: 14px; color: #333; cursor: pointer; margin-top: 8px;">
494                        <input type="checkbox" id="bls-notification-sound" class="bls-checkbox" checked>
495                        <span>Enable Notification Sound</span>
496                    </label>
497                </div>
498
499                <div style="margin-bottom: 15px;">
500                    <label style="display: block; font-size: 14px; font-weight: 600; color: #333; margin-bottom: 5px;">Check Interval (seconds)</label>
501                    <input type="number" id="bls-check-interval" class="bls-input" value="30" min="10" max="300">
502                </div>
503
504                <button id="bls-start-monitoring" class="bls-btn bls-btn-primary">Start Monitoring</button>
505                <button id="bls-stop-monitoring" class="bls-btn bls-btn-secondary" style="display: none;">Stop Monitoring</button>
506                <button id="bls-check-now" class="bls-btn bls-btn-secondary">Check Now</button>
507                <button id="bls-auto-fill-login" class="bls-btn bls-btn-secondary">Auto-Fill Login</button>
508            </div>
509        `;
510
511        document.body.appendChild(panel);
512        attachPanelEventListeners();
513        loadPanelSettings();
514    }
515
516    // Attach Event Listeners to Panel
517    function attachPanelEventListeners() {
518        // Toggle panel
519        document.getElementById('bls-toggle-panel').addEventListener('click', () => {
520            const content = document.getElementById('bls-panel-content');
521            const button = document.getElementById('bls-toggle-panel');
522            if (content.style.display === 'none') {
523                content.style.display = 'block';
524                button.textContent = '−';
525            } else {
526                content.style.display = 'none';
527                button.textContent = '+';
528            }
529        });
530
531        // Save credentials
532        document.getElementById('bls-save-credentials').addEventListener('click', async () => {
533            const email = document.getElementById('bls-email').value;
534            const password = document.getElementById('bls-password').value;
535            
536            if (email && password) {
537                await saveCredentials(email, password);
538                showPageNotification('Success', 'Credentials saved successfully!');
539            } else {
540                showPageNotification('Error', 'Please enter both email and password');
541            }
542        });
543
544        // Start monitoring
545        document.getElementById('bls-start-monitoring').addEventListener('click', async () => {
546            await startMonitoring();
547        });
548
549        // Stop monitoring
550        document.getElementById('bls-stop-monitoring').addEventListener('click', () => {
551            stopMonitoring();
552        });
553
554        // Check now
555        document.getElementById('bls-check-now').addEventListener('click', async () => {
556            await checkAppointmentSlots();
557        });
558
559        // Auto-fill login
560        document.getElementById('bls-auto-fill-login').addEventListener('click', async () => {
561            await autoFillLogin();
562        });
563
564        // Save config on change
565        document.getElementById('bls-auto-book').addEventListener('change', saveConfigFromPanel);
566        document.getElementById('bls-notification-sound').addEventListener('change', saveConfigFromPanel);
567        document.getElementById('bls-check-interval').addEventListener('change', saveConfigFromPanel);
568    }
569
570    // Load Panel Settings
571    async function loadPanelSettings() {
572        const credentials = await getCredentials();
573        const config = await getConfig();
574
575        document.getElementById('bls-email').value = credentials.email || '';
576        document.getElementById('bls-password').value = credentials.password || '';
577        document.getElementById('bls-auto-book').checked = config.autoBook || false;
578        document.getElementById('bls-notification-sound').checked = config.notificationSound !== false;
579        document.getElementById('bls-check-interval').value = (config.checkInterval || 30000) / 1000;
580    }
581
582    // Save Config from Panel
583    async function saveConfigFromPanel() {
584        const config = {
585            autoBook: document.getElementById('bls-auto-book').checked,
586            notificationSound: document.getElementById('bls-notification-sound').checked,
587            checkInterval: parseInt(document.getElementById('bls-check-interval').value) * 1000
588        };
589        
590        await saveConfig(config);
591        log('Config updated from panel');
592    }
593
594    // Start Monitoring
595    async function startMonitoring() {
596        if (isChecking) {
597            log('Monitoring already active');
598            return;
599        }
600
601        const config = await getConfig();
602        const interval = config.checkInterval || 30000;
603
604        isChecking = true;
605        updateMonitoringStatus(true);
606        
607        log(`Starting monitoring with ${interval/1000}s interval`);
608        showPageNotification('Monitoring Started', `Checking every ${interval/1000} seconds`);
609
610        // Initial check
611        await checkAppointmentSlots();
612
613        // Set up interval
614        checkInterval = setInterval(async () => {
615            await checkAppointmentSlots();
616        }, interval);
617    }
618
619    // Stop Monitoring
620    function stopMonitoring() {
621        if (!isChecking) {
622            log('Monitoring not active');
623            return;
624        }
625
626        isChecking = false;
627        updateMonitoringStatus(false);
628        
629        if (checkInterval) {
630            clearInterval(checkInterval);
631            checkInterval = null;
632        }
633
634        log('Monitoring stopped');
635        showPageNotification('Monitoring Stopped', 'Appointment checking has been stopped');
636    }
637
638    // Update Monitoring Status UI
639    function updateMonitoringStatus(active) {
640        const statusIndicator = document.getElementById('bls-status-indicator');
641        const statusText = document.getElementById('bls-status-text');
642        const startButton = document.getElementById('bls-start-monitoring');
643        const stopButton = document.getElementById('bls-stop-monitoring');
644
645        if (active) {
646            statusIndicator.className = 'bls-status bls-status-active';
647            statusText.textContent = 'Monitoring: Active';
648            startButton.style.display = 'none';
649            stopButton.style.display = 'block';
650        } else {
651            statusIndicator.className = 'bls-status bls-status-inactive';
652            statusText.textContent = 'Monitoring: Inactive';
653            startButton.style.display = 'block';
654            stopButton.style.display = 'none';
655        }
656    }
657
658    // Capture Form Data for Future Auto-fill
659    function captureFormData() {
660        const forms = document.querySelectorAll('form');
661        
662        forms.forEach(form => {
663            form.addEventListener('submit', async (e) => {
664                const formData = {};
665                const inputs = form.querySelectorAll('input, select, textarea');
666                
667                inputs.forEach(input => {
668                    if (input.name && input.value && input.type !== 'password') {
669                        formData[input.name] = input.value;
670                    }
671                });
672
673                if (Object.keys(formData).length > 0) {
674                    await saveFormData(formData);
675                    log('Form data captured and saved');
676                }
677            });
678        });
679    }
680
681    // Initialize Extension
682    async function init() {
683        log('BLS Auto Appointment Booker initialized');
684        
685        // Wait for page to load
686        if (document.readyState === 'loading') {
687            document.addEventListener('DOMContentLoaded', init);
688            return;
689        }
690
691        // Wait a bit for dynamic content to load
692        setTimeout(() => {
693            // Create control panel
694            createControlPanel();
695            
696            // Auto-fill login if on login page
697            if (window.location.href.includes('login')) {
698                setTimeout(() => autoFillLogin(), 1000);
699            }
700            
701            // Capture form data for future use
702            captureFormData();
703            
704            // Set up mutation observer for dynamic content
705            const observer = new MutationObserver(debounce(() => {
706                if (!document.getElementById('bls-control-panel')) {
707                    createControlPanel();
708                }
709            }, 1000));
710            
711            observer.observe(document.body, {
712                childList: true,
713                subtree: true
714            });
715            
716            log('Extension fully loaded and ready');
717        }, 2000);
718    }
719
720    // Start the extension
721    init();
722
723})();
BLS Spain Morocco Auto Appointment Booker | Robomonkey