Skip to content

Instantly share code, notes, and snippets.

@fdns
Created January 7, 2026 09:35
Show Gist options
  • Select an option

  • Save fdns/c2957d28ee5c020c48c0c8186d9b0b1f to your computer and use it in GitHub Desktop.

Select an option

Save fdns/c2957d28ee5c020c48c0c8186d9b0b1f to your computer and use it in GitHub Desktop.
// ==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