Created
January 7, 2026 09:35
-
-
Save fdns/c2957d28ee5c020c48c0c8186d9b0b1f to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // ==UserScript== | |
| // @name DVSA Speed Booker (Modal Lock & High Speed) | |
| // @namespace http://tampermonkey.net/ | |
| // @version 3.4 | |
| // @description Single-click enforcement for modal and slot submission. | |
| // @author Gemini | |
| // @match https://driverpracticaltest.dvsa.gov.uk/application* | |
| // @run-at document-idle | |
| // @grant none | |
| // ==/UserScript== | |
| (function() { | |
| 'use strict'; | |
| const TARGET_CENTRES = ['Barking', 'Goodmayes', 'Wanstead', 'Chingford', 'Loughton', 'Hornchurch', 'Tilbury', 'Brentwood'/*, 'Belvedere', 'Erith', 'Bromley','Tottenham'*/]; | |
| const MIN_REFRESH = 45; | |
| const MAX_REFRESH = 60; | |
| // --- STATE LOCKS --- | |
| let hasClickedModal = false; | |
| let hasClickedSubmit = false; | |
| let hasClickedDate = false; | |
| const log = (msg, color = "#1d70b8") => { | |
| console.log(`%c[${new Date().toLocaleTimeString()}] ${msg}`, `color: ${color}; font-weight: bold;`); | |
| }; | |
| // --- PHASE A: SEARCHING --- | |
| function runSearchPhase() { | |
| const resultItems = document.querySelectorAll('.test-centre-results li'); | |
| if (resultItems.length === 0) return; | |
| let foundMatch = false; | |
| resultItems.forEach(item => { | |
| const name = item.querySelector('h4')?.innerText || ""; | |
| const status = item.querySelector('h5')?.innerText || ""; | |
| const link = item.querySelector('.test-centre-details-link'); | |
| if (TARGET_CENTRES.some(t => name.includes(t)) && status.toLowerCase().includes('available')) { | |
| log(`🎯 MATCH: ${name}`, "green"); | |
| link.click(); | |
| foundMatch = true; | |
| } | |
| }); | |
| if (!foundMatch) scheduleRefresh(); | |
| } | |
| let refreshScheduled = false; | |
| function scheduleRefresh() { | |
| if (refreshScheduled) return; | |
| refreshScheduled = true; | |
| const wait = Math.floor(Math.random() * (MAX_REFRESH - MIN_REFRESH + 1) + MIN_REFRESH) * 1000; | |
| log(`No match, waiting ${wait/1000}s`) | |
| setTimeout(() => { | |
| const btn = document.getElementById('fetch-more-centres') || document.getElementById('test-centres-submit'); | |
| btn ? btn.click() : location.reload(); | |
| }, wait); | |
| } | |
| // --- PHASE B: BOOKING (With Single-Click Enforcement) --- | |
| function runBookingPhase() { | |
| // 1. MODAL (Highest Priority) | |
| const modalBtn = document.getElementById('slot-warning-continue'); | |
| const isVisible = modalBtn && (modalBtn.offsetWidth > 0 || modalBtn.offsetHeight > 0); | |
| if (isVisible && !hasClickedModal) { | |
| log("✔️ MODAL: Clicking Continue once...", "#d4351c"); | |
| hasClickedModal = true; // LOCK | |
| modalBtn.focus(); | |
| modalBtn.click(); | |
| return; | |
| } | |
| // 2. SLOT SELECTION | |
| const slot = document.querySelector('.SlotPicker-slot'); | |
| const submit = document.getElementById('slot-chosen-submit'); | |
| if (slot && submit && !hasClickedSubmit && !isVisible && hasClickedDate) { | |
| if (!slot.checked) { | |
| slot.click(); | |
| log("✔️ Slot selected."); | |
| } else { | |
| log("✔️ SUBMIT: Clicking Continue once..."); | |
| hasClickedSubmit = true; // LOCK | |
| submit.click(); | |
| } | |
| // Recursive check (200ms for instant reaction) | |
| setTimeout(monitorBooking, 200); | |
| return; | |
| } | |
| // 3. CALENDAR | |
| const date = document.querySelector('.BookingCalendar-date--bookable .BookingCalendar-dateLink'); | |
| if (date && !hasClickedDate) { | |
| log("✔️ CALENDAR: Selecting date..."); | |
| hasClickedDate = true; // LOCK | |
| date.click(); | |
| // Recursive check (200ms for instant reaction) | |
| setTimeout(monitorBooking, 200); | |
| return; | |
| } | |
| log("No action detected"); | |
| // Recursive check (200ms for instant reaction) | |
| setTimeout(monitorBooking, 200); | |
| } | |
| function monitorBooking() { | |
| const isBooking = !!( | |
| document.querySelector('.SlotPicker-slot') || | |
| document.querySelector('.BookingCalendar-day') || | |
| document.getElementById('slot-warning-continue') | |
| ); | |
| if (isBooking) runBookingPhase(); | |
| } | |
| // Entry Point | |
| if (document.querySelector('.SlotPicker-slot') || document.querySelector('.BookingCalendar-day')) { | |
| runBookingPhase(); | |
| } else { | |
| setTimeout(runSearchPhase, 1000); | |
| } | |
| })(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment