Skip to content

Instantly share code, notes, and snippets.

@Rovack
Last active July 29, 2025 17:10
Show Gist options
  • Save Rovack/51e0fb558ee0fa4ce0e2cd5f0ab17cb1 to your computer and use it in GitHub Desktop.
Save Rovack/51e0fb558ee0fa4ce0e2cd5f0ab17cb1 to your computer and use it in GitHub Desktop.
A simple script that waits for tickets to become available for the Harry Potter Studio Tour in London, and grabs them
// Note that this has some limitations, such as looking specifically for adult tickets,
// looking for the given days only in the nearest month that has availability,
// and always choosing the earliest time if several are found within the desired dates.
function setAdultTickets(adultTicketsWanted) {
const adultTicketsCount = parseInt($('.quantity-control.row > input')[0].value, 10);
const ticketChangeIterations = Math.abs(adultTicketsWanted - adultTicketsCount);
const ticketChangeButton = $(`.quantity-control.row > button.typcn-${adultTicketsCount < adultTicketsWanted ? 'plus' : 'minus'}`)[0];
for (let i = 0; i < ticketChangeIterations; i++) {
ticketChangeButton.click();
}
}
function playSound(src) {
return new Promise((resolve) => {
const audio = new Audio(src);
audio.onended = resolve;
audio.play();
});
}
function repeatHeyListen() {
playSound('https://www.myinstants.com/media/sounds/hey_listen.mp3')
.then(repeatHeyListen);
}
function waitForAvailability() {
return new Promise((resolve) => {
setTimeout(() => {
const availableEls = $('.calendar>.row:not(.blankLoader) .calendar-body .day.available'), isLoading = $('.calendar-modal[data-component=eventTimeModal] .modal-content > .loading-mask.hide').length === 0;
if (isLoading) {
return waitForAvailability()
.then((res) => resolve(res));
}
resolve(availableEls);
}, 1000);
});
}
function playSounds() {
playSound('https://www.myinstants.com/media/sounds/mlg-airhorn.mp3')
.then(() => playSound('https://www.myinstants.com/media/sounds/sound-9______.mp3'))
.then(() => playSound('https://www.myinstants.com/media/sounds/ps_1.mp3'))
.then(() => playSound('https://www.myinstants.com/media/sounds/wrong-answer-sound-effect.mp3'))
.then(() => playSound('https://www.myinstants.com/media/sounds/lalalalala.swf.mp3'))
.then(() => playSound('https://www.myinstants.com/media/sounds/tuturu_1.mp3'))
.then(() => playSound('https://www.myinstants.com/media/sounds/hallelujahshort.swf.mp3'))
.then(repeatHeyListen);
}
function addTicketsToBasket(dayElement) {
dayElement.click();
setTimeout(() => waitForAvailability()
.then(() => {
$('.ui-control.button.select-time')[0].click();
setTimeout(() => {
$('.typcn.typcn-shopping-cart.ng-binding')[0].click();
}, 2000);
}), 2000);
}
function checkForTickets(datesWanted=[6, 7, 8], adultTicketsWanted=2, checkFrequency=15) {
setAdultTickets(adultTicketsWanted);
function check() {
$('.shared-calendar-button').click();
waitForAvailability()
.then(availableEls => {
console.log(new Date(), 'Availability loaded. Checking for relevant dates...');
for (let i = 0; i < availableEls.length; i++) {
const day = parseInt(availableEls[i].innerText, 10);
console.log('Day', day, 'is available...');
if (datesWanted.includes(day)) {
console.log('Found tickets!!!!!');
playSounds();
addTicketsToBasket(availableEls[i]);
return;
}
}
console.log(`Relevant dates not yet available. Will check again in ${checkFrequency} seconds.`);
$('#page > div:nth-child(11) > div.modal.info-modal.w-auto-c > div > div.close').click();
setTimeout(check, checkFrequency * 1000);
});
};
check();
}
@Rovack
Copy link
Author

Rovack commented Mar 21, 2024

Hi @AntoineGpp, thanks for bringing this up! Apparently they've made some minor changes to the website code, which require tweaking the script. Namely, the line:

const availableEls = $('.c-14-all.available'), isLoading = $('.calendar-modal[data-component=eventTimeModal] .modal-content > .loading-container.hide').length === 0;

Needs to be changed to:

const availableEls = $('.calendar>.row:not(.blankLoader) .calendar-body .day.available'), isLoading = $('.calendar-modal[data-component=eventTimeModal] .modal-content > .loading-mask.hide').length === 0;

I haven't tested these changes very extensively, so there may be some edge cases I'm missing, but the simple flow at least appears to work.
If you do run into any additional issues, please let me know and I can see what else needs to be adjusted.

In the meanwhile, I'll correct all the existing code snippets to use this updated line. Thanks again, and hope this helps! :)

@AntoineGpp
Copy link

AntoineGpp commented Mar 21, 2024

Thank you for your fast reply !
It's almost working now, I can see the calendar reloading every fifteen seconds. However, my console doesn't show me any message after starting the script, else than "undefined".
Maybe there's something to activate in my browser ? I'm using Brave with shield off.

EDIT: I tried the script in Edge, and it seems to work now. I'll keep you updated !
Thanks a lot man :)

@Mirtel123
Copy link

Mirtel123 commented Mar 23, 2024

Hello everyone,
i just tested the script on a day that i know its already available. But the script tell me, the Relevant dates not yet available. Can anybody help me about this?
Thats my code:

function setAdultTickets(adultTicketsWanted) {
	const adultTicketsCount = parseInt($('.quantity-control.row > input')[0].value, 10);
	const ticketChangeIterations = Math.abs(adultTicketsWanted - adultTicketsCount);
	const ticketChangeButton = $(`.quantity-control.row > button.typcn-${adultTicketsCount < adultTicketsWanted ? 'plus' : 'minus'}`)[0];

	for (let i = 0; i < ticketChangeIterations; i++) {
		ticketChangeButton.click();
	}
}

function playSound(src) {
	return new Promise((resolve) => {
		const audio = new Audio(src);
		audio.onended = resolve;
		audio.play();
	});
}

function repeatHeyListen() {
	playSound('https://www.myinstants.com/media/sounds/hey_listen.mp3')
		.then(repeatHeyListen);
}

function waitForAvailability(monthWanted) {
	return new Promise((resolve) => {
		setTimeout(() => {
			const availableEls = $('.calendar>.row:not(.blankLoader) .calendar-body .day.available'), isLoading = $('.calendar-modal[data-component=eventTimeModal] .modal-content > .loading-mask.hide').length === 0;
			if (isLoading) {
				return waitForAvailability(monthWanted)
					.then((res) => resolve(res));
			}

			if (monthWanted == null) {
				resolve({ availableEls });
			}

			const monthValue = $('[name="ctl00$ContentPlaceHolder$SalesChannelDetailControl$EventsDateTimeSelectorModal$EventsDateTimeSelector$CalendarSelector$MonthDropDownList"]')[0].value;
			const month = parseInt(monthValue.replace(/^\D+/g, ''), 10);

			if (month < monthWanted) {
				console.log(`Month too early (${month}) - skipping to next month.`);
				$('[name="ctl00$ContentPlaceHolder$SalesChannelDetailControl$EventsDateTimeSelectorModal$EventsDateTimeSelector$CalendarSelector$NextMonthImageButton"]').click();
				return waitForAvailability(monthWanted).then(res => resolve(res));
			}

			resolve({ availableEls, month });
		}, 1000);
	});
}

function playSounds() {
	playSound('https://www.myinstants.com/media/sounds/mlg-airhorn.mp3')
		.then(() => playSound('https://www.myinstants.com/media/sounds/sound-9______.mp3'))
		.then(() => playSound('https://www.myinstants.com/media/sounds/ps_1.mp3'))
		.then(() => playSound('https://www.myinstants.com/media/sounds/wrong-answer-sound-effect.mp3'))
		.then(() => playSound('https://www.myinstants.com/media/sounds/lalalalala.swf.mp3'))
		.then(() => playSound('https://www.myinstants.com/media/sounds/tuturu_1.mp3'))
		.then(() => playSound('https://www.myinstants.com/media/sounds/hallelujahshort.swf.mp3'))
		.then(repeatHeyListen);
}

async function addTicketsToBasket(dayElement) {
	dayElement.click();

	await new Promise((resolve) => setTimeout(resolve, 2000));
	await waitForAvailability();

	const chooseTimeButton = $('.ui-control.button.select-time:not(.disabled)')[0];
	if (!chooseTimeButton) return false;

	console.log('Found tickets!!!!!');
	playSounds();

	chooseTimeButton.click();

	await new Promise((resolve) => setTimeout(resolve, 2000));

	$('.typcn.typcn-shopping-cart.ng-binding')[0].click();
	return true;
}

function checkForTickets(datesWanted=[6, 7, 8], adultTicketsWanted=2, checkFrequency=15, monthWanted) {
	setAdultTickets(adultTicketsWanted);

	async function check() {
		if ($('.ui-control.button.extendSession').length != 0) {
			console.log('Extending session');
			$('.ui-control.button.extendSession').click();
		}
		
		$('.shared-calendar-button').click();

		waitForAvailability(monthWanted)
			.then(async ({ availableEls, month }) => {
				console.log(new Date(), `Availability loaded${month != null ? ` for month ${month}` : ''}. Checking for relevant dates...`);

				if (monthWanted != null && month > monthWanted) {
					console.log(`Month is too late (${month}). Will check again in ${checkFrequency} seconds.`);
					setTimeout(check, checkFrequency * 1000);
					return;
				}

				for (let i = 0; i < availableEls.length; i++) {
					const day = parseInt($('.c-14-all.available')[i].innerText, 10);
					console.log('Day', day, 'is available...');
					if (datesWanted.includes(day)) {
						const succeeded = await addTicketsToBasket($('.c-14-all.available')[i]);
						if (succeeded) return;
					}
				}

				console.log(`Relevant dates not yet available. Will check again in ${checkFrequency} seconds.`);
				setTimeout(check, checkFrequency * 1000);
		});
	};
	check();
}

function checkForTicketsInMonth(datesWanted, monthWanted, adultTicketsWanted, checkFrequency) {
	return checkForTickets(datesWanted, adultTicketsWanted, checkFrequency, monthWanted);
}

and thats what i´m looking for
checkForTicketsInMonth([3, 4], 5, 1, 3)

on the 3rd are available time slots

the Console say:

Day NaN is available...
Relevant dates not yet available. Will check again in 15 seconds.

thank you very much for your help!
best regards Martin

@Rovack
Copy link
Author

Rovack commented Mar 23, 2024

Hey @Mirtel123, looks like this is because when I was updating snippets throughout these comments (as per this), I missed one that was still using .c-14-all.available as the selector.

I've now updated that one too, replacing .c-14-all.available (in both lines in which it appears) with .calendar>.row:not(.blankLoader) .calendar-body .day.available, and it seems like that should work.
Please let me know if there are still any problems after this change. Thanks!

@Mirtel123
Copy link

Mirtel123 commented Mar 23, 2024

OMG it works great 👍 thank u very much! Do you think its possible to pretend the time for the tickets? I mean only tickets befor 12 o´clock or similar?

@AntoineGpp
Copy link

I finally got my tickets for the month of May ! It took me 2,5 days to get them 🤯
First experience with javascript, so interesting to see how it works!
I'm truly grateful @Rovack & everyone who took part in building this!

@Rovack
Copy link
Author

Rovack commented Mar 24, 2024

Amazing to hear @AntoineGpp! Hope you have an awesome time there.
And glad this proved a not-too-painful intro to JS. 😄

And @Mirtel123, sure! I think some changes to addTicketsToBasket should mostly do the trick - just replace it with this version:

async function addTicketsToBasket(dayElement, { minHour, maxHour } = {}) {
	dayElement.click();

	await new Promise((resolve) => setTimeout(resolve, 2000));
	await waitForAvailability();

	const timeRows = $('.time-selector .times .time.row');
	for (let i = 0; i < timeRows.length; i++) {
		const row = $(timeRows[i]);
		const timeString = row.find('.time')[0].innerText;
		const chooseTimeButton = row.find('.select-time:not(.disabled)')[0];

		if (!chooseTimeButton) continue;

		const hour = parseInt(timeString.split(':')[0], 10);
		if (minHour != null && hour < minHour) {
			console.log(`Tickets found at wanted date but time is too early (${timeString})`);
			continue;
		}
		if (maxHour != null && hour > maxHour) {
			console.log(`Tickets found at wanted date but time is too late (${timeString})`);
			continue;
		}

		console.log('Found tickets!!!!!');
		playSounds();
	
		chooseTimeButton.click();
	
		await new Promise((resolve) => setTimeout(resolve, 2000));
	
		$('.typcn.typcn-shopping-cart.ng-binding')[0].click();
		return true;	
	}

	return false;
}

Then we just need to pass the hour range around. To that end, the 1st line in checkForTickets becomes:

function checkForTickets(datesWanted=[6, 7, 8], adultTicketsWanted=2, checkFrequency=15, monthWanted, hourRange) {

And the line inside it that references addTicketsToBasket changes into:

const succeeded = await addTicketsToBasket($('.calendar>.row:not(.blankLoader) .calendar-body .day.available')[i], hourRange);

Finally checkForTicketsInMonth becomes:

function checkForTicketsInMonth(datesWanted, monthWanted, adultTicketsWanted, checkFrequency, hourRange) {
	return checkForTickets(datesWanted, adultTicketsWanted, checkFrequency, monthWanted, hourRange);
}

At that point, you should be able to run e.g. checkForTicketsInMonth([3, 4], 5, 1, 3, { minHour: 0, maxHour: 12 }), to only accept tickets midnight to 12 PM (inclusive; if you want entry at 11:30 at the latest, then just change 12 to 11).
If you'd rather be able to specify non-consecutive ranges, or if half hours are important as well, it should be easy enough to adjust this to take an array of specific acceptable times instead of just a min and max hour. Just let me know, and I can tweak it as needed.

Hope this works, and let me know if you run into any further issues!

@Mirtel123
Copy link

Hello @Rovack i just tried it and if its on a day with available timeslots it works very fine 👍 but with a error:
this is my code now and bellow you see the errors

function setAdultTickets(adultTicketsWanted) {
	const adultTicketsCount = parseInt($('.quantity-control.row > input')[0].value, 10);
	const ticketChangeIterations = Math.abs(adultTicketsWanted - adultTicketsCount);
	const ticketChangeButton = $(`.quantity-control.row > button.typcn-${adultTicketsCount < adultTicketsWanted ? 'plus' : 'minus'}`)[0];

	for (let i = 0; i < ticketChangeIterations; i++) {
		ticketChangeButton.click();
	}
}

function playSound(src) {
	return new Promise((resolve) => {
		const audio = new Audio(src);
		audio.onended = resolve;
		audio.play();
	});
}

function repeatHeyListen() {
	playSound('https://www.myinstants.com/media/sounds/hey_listen.mp3')
		.then(repeatHeyListen);
}

function waitForAvailability(monthWanted) {
	return new Promise((resolve) => {
		setTimeout(() => {
			const availableEls = $('.calendar>.row:not(.blankLoader) .calendar-body .day.available'), isLoading = $('.calendar-modal[data-component=eventTimeModal] .modal-content > .loading-mask.hide').length === 0;
			if (isLoading) {
				return waitForAvailability(monthWanted)
					.then((res) => resolve(res));
			}

			if (monthWanted == null) {
				resolve({ availableEls });
			}

			const monthValue = $('[name="ctl00$ContentPlaceHolder$SalesChannelDetailControl$EventsDateTimeSelectorModal$EventsDateTimeSelector$CalendarSelector$MonthDropDownList"]')[0].value;
			const month = parseInt(monthValue.replace(/^\D+/g, ''), 10);

			if (month < monthWanted) {
				console.log(`Month too early (${month}) - skipping to next month.`);
				$('[name="ctl00$ContentPlaceHolder$SalesChannelDetailControl$EventsDateTimeSelectorModal$EventsDateTimeSelector$CalendarSelector$NextMonthImageButton"]').click();
				return waitForAvailability(monthWanted).then(res => resolve(res));
			}

			resolve({ availableEls, month });
		}, 1000);
	});
}

function playSounds() {
	playSound('https://www.myinstants.com/media/sounds/mlg-airhorn.mp3')
		.then(() => playSound('https://www.myinstants.com/media/sounds/sound-9______.mp3'))
		.then(() => playSound('https://www.myinstants.com/media/sounds/ps_1.mp3'))
		.then(() => playSound('https://www.myinstants.com/media/sounds/wrong-answer-sound-effect.mp3'))
		.then(() => playSound('https://www.myinstants.com/media/sounds/lalalalala.swf.mp3'))
		.then(() => playSound('https://www.myinstants.com/media/sounds/tuturu_1.mp3'))
		.then(() => playSound('https://www.myinstants.com/media/sounds/hallelujahshort.swf.mp3'))
		.then(repeatHeyListen);
}

async function addTicketsToBasket(dayElement, { minHour, maxHour } = {}) {
	dayElement.click();

	await new Promise((resolve) => setTimeout(resolve, 2000));
	await waitForAvailability();

	const timeRows = $('.time-selector .times .time.row');
	for (let i = 0; i < timeRows.length; i++) {
		const row = $(timeRows[i]);
		const timeString = row.find('.time')[0].innerText;
		const chooseTimeButton = row.find('.select-time:not(.disabled)')[0];

		if (!chooseTimeButton) continue;

		const hour = parseInt(timeString.split(':')[0], 10);
		if (minHour != null && hour < minHour) {
			console.log(`Tickets found at wanted date but time is too early (${timeString})`);
			continue;
		}
		if (maxHour != null && hour > maxHour) {
			console.log(`Tickets found at wanted date but time is too late (${timeString})`);
			continue;
		}

		console.log('Found tickets!!!!!');
		playSounds();
	
		chooseTimeButton.click();
	
		await new Promise((resolve) => setTimeout(resolve, 2000));
	
		$('.typcn.typcn-shopping-cart.ng-binding')[0].click();
		return true;	
	}

	return false;
}
function checkForTickets(datesWanted=[6, 7, 8], adultTicketsWanted=2, checkFrequency=15, monthWanted, hourRange) {
	setAdultTickets(adultTicketsWanted);

	async function check() {
		if ($('.ui-control.button.extendSession').length != 0) {
			console.log('Extending session');
			$('.ui-control.button.extendSession').click();
		}
		
		$('.shared-calendar-button').click();

		waitForAvailability(monthWanted)
			.then(async ({ availableEls, month }) => {
				console.log(new Date(), `Availability loaded${month != null ? ` for month ${month}` : ''}. Checking for relevant dates...`);

				if (monthWanted != null && month > monthWanted) {
					console.log(`Month is too late (${month}). Will check again in ${checkFrequency} seconds.`);
					setTimeout(check, checkFrequency * 1000);
					return;
				}

				for (let i = 0; i < availableEls.length; i++) {
					const day = parseInt($('.calendar>.row:not(.blankLoader) .calendar-body .day.available')[i].innerText, 10);
					console.log('Day', day, 'is available...');
					if (datesWanted.includes(day)) {
						const succeeded = await addTicketsToBasket($('.calendar>.row:not(.blankLoader) .calendar-body .day.available')[i], hourRange);
						if (succeeded) return;
					}
				}

				console.log(`Relevant dates not yet available. Will check again in ${checkFrequency} seconds.`);
				setTimeout(check, checkFrequency * 1000);
		});
	};
	check();
}

function checkForTicketsInMonth(datesWanted, monthWanted, adultTicketsWanted, checkFrequency, hourRange) {
	return checkForTickets(datesWanted, adultTicketsWanted, checkFrequency, monthWanted, hourRange);
}

VM233:37 Uncaught TypeError: Cannot read properties of undefined (reading 'value')
at :37:176

  | (anonymous) | @ | VM233:37
  | setTimeout (async) |   |  
  | (anonymous) | @ | VM233:26
  | waitForAvailability | @ | VM233:25
  | addTicketsToBasket | @ | VM233:66
  | await in addTicketsToBasket (async) |   |  
  | (anonymous) | @ | VM233:124
  | Promise.then (async) |   |  
  | check | @ | VM233:111
  | checkForTickets | @ | VM233:133
  | checkForTicketsInMonth | @ | VM233:137
  | (anonymous) | @ | VM238:1

and

Uncaught (in promise) TypeError: Cannot read properties of undefined (reading 'innerText')
at :121:97
  | (anonymous) | @ | VM233:121
  | setTimeout (async) |   |  
  | (anonymous) | @ | VM233:26
  | waitForAvailability | @ | VM233:25
  | addTicketsToBasket | @ | VM233:66
  | await in addTicketsToBasket (async) |   |  
  | (anonymous) | @ | VM233:124
  | Promise.then (async) |   |  
  | check | @ | VM233:111
  | checkForTickets | @ | VM233:133
  | checkForTicketsInMonth | @ | VM233:137
  | (anonymous) | @ | VM238:1

it is impressive, how fast you can do this coding!
Thank you for your support and patience

@Rovack
Copy link
Author

Rovack commented Mar 24, 2024

Hmm, how interesting... I haven't been able to get it to happen for me, but sounds like it could be a result of the race condition mentioned here, which may become more likely once we add hour constraints.

If you could send the exact parameters you pass to checkForTicketsInMonth, maybe I'll be able to see how it happens. Or, if you could send the lines printed above the error, so I can see what was happening that led up to it, that could help as well.

Of course, if this happens randomly only every so often, and not in some deterministic fashion, one workaround would be to just automatically ignore the error and continue. I think you might be able to achieve this by turning checkForTickets into:

function checkForTickets(datesWanted=[6, 7, 8], adultTicketsWanted=2, checkFrequency=15, monthWanted, hourRange) {
	setAdultTickets(adultTicketsWanted);

	async function check() {
		try {
			if ($('.ui-control.button.extendSession').length != 0) {
				console.log('Extending session');
				$('.ui-control.button.extendSession').click();
			}
			
			$('.shared-calendar-button').click();

			await waitForAvailability(monthWanted)
				.then(async ({ availableEls, month }) => {
					console.log(new Date(), `Availability loaded${month != null ? ` for month ${month}` : ''}. Checking for relevant dates...`);

					if (monthWanted != null && month > monthWanted) {
						console.log(`Month is too late (${month}). Will check again in ${checkFrequency} seconds.`);
						setTimeout(check, checkFrequency * 1000);
						return;
					}

					for (let i = 0; i < availableEls.length; i++) {
						const day = parseInt($('.calendar>.row:not(.blankLoader) .calendar-body .day.available')[i].innerText, 10);
						console.log('Day', day, 'is available...');
						if (datesWanted.includes(day)) {
							const succeeded = await addTicketsToBasket($('.calendar>.row:not(.blankLoader) .calendar-body .day.available')[i], hourRange);
							if (succeeded) return;
						}
					}

					console.log(`Relevant dates not yet available. Will check again in ${checkFrequency} seconds.`);
					setTimeout(check, checkFrequency * 1000);
			});
		} catch (err) {
			console.error('Error checking. Just gonna keep trying.', err);
			setTimeout(check, checkFrequency * 1000);
		}
	};
	check();
}

@Mirtel123
Copy link

@Rovack today i tried it again and it works perfectly 👍 i dont now why it dosn‘t the last time. Thank u very much for your support

@Rovack
Copy link
Author

Rovack commented Mar 25, 2024

Great! Glad to hear it @Mirtel123.
Let me know how it turns out or if you run into any further issues. :)

@gokhanalpdogan
Copy link

gokhanalpdogan commented Apr 16, 2024

Hello there,

Sorry for this dumb questions but really i need to ask :/

i've modified the script in which days i want to buy. (14 - 15 may) here is the script code;


// Note that this has some limitations, such as looking specifically for adult tickets,
// looking for the given days only in the nearest month that has availability,
// and always choosing the earliest time if several are found within the desired dates.

function setAdultTickets(adultTicketsWanted) {
const adultTicketsCount = parseInt($('.quantity-control.row > input')[0].value, 10);
const ticketChangeIterations = Math.abs(adultTicketsWanted - adultTicketsCount);
const ticketChangeButton = $(.quantity-control.row > button.typcn-${adultTicketsCount < adultTicketsWanted ? 'plus' : 'minus'})[0];

for (let i = 0; i < ticketChangeIterations; i++) {
	ticketChangeButton.click();
}

}

function playSound(src) {
return new Promise((resolve) => {
const audio = new Audio(src);
audio.onended = resolve;
audio.play();
});
}

function repeatHeyListen() {
playSound('https://www.myinstants.com/media/sounds/hey_listen.mp3')
.then(repeatHeyListen);
}

function waitForAvailability() {
return new Promise((resolve) => {
setTimeout(() => {
const availableEls = $('.c-14-all.available')
if (availableEls.length === 0) {
return waitForAvailability()
.then((res) => resolve(res));
}

		resolve(availableEls);
	}, 1000);
});

}

function playSounds() {
playSound('https://www.myinstants.com/media/sounds/mlg-airhorn.mp3')
.then(() => playSound('https://www.myinstants.com/media/sounds/sound-9______.mp3'))
.then(() => playSound('https://www.myinstants.com/media/sounds/ps_1.mp3'))
.then(() => playSound('https://www.myinstants.com/media/sounds/wrong-answer-sound-effect.mp3'))
.then(() => playSound('https://www.myinstants.com/media/sounds/lalalalala.swf.mp3'))
.then(() => playSound('https://www.myinstants.com/media/sounds/tuturu_1.mp3'))
.then(() => playSound('https://www.myinstants.com/media/sounds/hallelujahshort.swf.mp3'))
.then(repeatHeyListen);
}

function addTicketsToBasket(dayElement) {
dayElement.click();

waitForAvailability()
	.then(() => {
		$('.ui-control.button.select-time')[0].click();

		setTimeout(() => {
			$('.typcn.typcn-shopping-cart.ng-binding')[0].click();
		}, 2000);
	});

}

function checkForTickets(datesWanted=[14, 15], adultTicketsWanted=2, checkFrequency=15) {
setAdultTickets(adultTicketsWanted);

function check() {
	$('.shared-calendar-button').click();

	waitForAvailability()
		.then(availableEls => {
			console.log(new Date(), 'Availability loaded. Checking for relevant dates...');
			for (let i = 0; i < availableEls.length; i++) {
				const day = parseInt(availableEls[i].innerText, 10);
				console.log('Day', day, 'is available...');
				if (datesWanted.includes(day)) {
					console.log('Found tickets!!!!!');
					playSounds();
					addTicketsToBasket(availableEls[i]);
					return;
				}
			}

			console.log(`Relevant dates not yet available. Will check again in ${checkFrequency} seconds.`);
			$('#page > div:nth-child(11) > div.modal.info-modal.w-auto-c > div > div.close').click();
			setTimeout(check, checkFrequency * 1000);
	});
};
check();

}

i just updated datesWanted.

here are the questions;

  1. when i run script, google chrome says ''undefined'' i think this is normal but i have to be sure :)
  2. after a while webpage popups an error ''SESSION WARNING
    Your session has expired.
    START OVER''

is it normal?

again sorry for disturb.
thanks for your help.

@Rovack
Copy link
Author

Rovack commented Apr 17, 2024

Hi @gokhanalpdogan! First of all, yes: getting "undefined" is normal. Just make sure after you run that, you also actually use checkForTickets, as described here, because until you do nothing is actually happening. That's also actually where you're supposed to specify the dates, although the way you did it should be fine too.

That same comment also mentions, near the bottom, how to deal with these expiring sessions, by linking here where there's a solution for that.

Hope this helps, but let me know if there are any further questions or complications.

@chfrodin
Copy link

chfrodin commented Apr 30, 2024

Hey @Rovack I just wanted to say a big thank you for this one. Got it up and running, took about five days straight use and I was able to get four tickets for next week.

@Rovack
Copy link
Author

Rovack commented May 3, 2024

@chfrodin That's awesome, makes me really happy to hear - thanks so much for letting me know! 😄

@wired14
Copy link

wired14 commented Feb 25, 2025

How do you run this script to start? Not a web developer.

@wired14
Copy link

wired14 commented Feb 25, 2025

How do you run this script to start? Not a web developer.

I was able to get it working by using console in edge. Thanks for the code! Will update if I get some. Booking out two months, but barely missed the open window for when I'll be there.

@Rovack
Copy link
Author

Rovack commented Feb 25, 2025

Awesome, glad you got it running @wired14! :) Definitely let me know if you run into any problems.

@wired14
Copy link

wired14 commented Mar 17, 2025

Awesome, glad you got it running @wired14! :) Definitely let me know if you run into any problems.

Was finally able to get tickets today. @Rovack. I had to use the modified version. It would time out after a day or so and I would have to re-run. I was able to get tickets this morning when I logged in to refresh the script and it immediately went off. I had to run two scripts to catch days in April and May.

Additionally, there is a queue system used occasionally, but only to initially enter the site. Also, not sure how the hourRange was used, but worked as is.

Example usage:

function setAdultTickets(adultTicketsWanted) {
	const adultTicketsCount = parseInt($('.quantity-control.row > input')[0].value, 4);
	const ticketChangeIterations = Math.abs(adultTicketsWanted - adultTicketsCount);
	const ticketChangeButton = $(`.quantity-control.row > button.typcn-${adultTicketsCount < adultTicketsWanted ? 'plus' : 'minus'}`)[0];

	for (let i = 0; i < ticketChangeIterations; i++) {
		ticketChangeButton.click();
	}
}

function playSound(src) {
	return new Promise((resolve) => {
		const audio = new Audio(src);
		audio.onended = resolve;
		audio.play();
	});
}

function repeatHeyListen() {
	playSound('https://www.myinstants.com/media/sounds/hey_listen.mp3')
		.then(repeatHeyListen);
}

function waitForAvailability(monthWanted) {
	return new Promise((resolve) => {
		setTimeout(() => {
			const availableEls = $('.calendar>.row:not(.blankLoader) .calendar-body .day.available'), isLoading = $('.calendar-modal[data-component=eventTimeModal] .modal-content > .loading-mask.hide').length === 0;
			if (isLoading) {
				return waitForAvailability(monthWanted)
					.then((res) => resolve(res));
			}

			if (monthWanted == null) {
				resolve({ availableEls });
			}

			const monthValue = $('[name="ctl00$ContentPlaceHolder$SalesChannelDetailControl$EventsDateTimeSelectorModal$EventsDateTimeSelector$CalendarSelector$MonthDropDownList"]')[0].value;
			const month = parseInt(monthValue.replace(/^\D+/g, ''), 10);

			if (month < monthWanted) {
				console.log(`Month too early (${month}) - skipping to next month.`);
				$('[name="ctl00$ContentPlaceHolder$SalesChannelDetailControl$EventsDateTimeSelectorModal$EventsDateTimeSelector$CalendarSelector$NextMonthImageButton"]').click();
				return waitForAvailability(monthWanted).then(res => resolve(res));
			}

			resolve({ availableEls, month });
		}, 1000);
	});
}

function playSounds() {
	playSound('https://www.myinstants.com/media/sounds/mlg-airhorn.mp3')
		.then(() => playSound('https://www.myinstants.com/media/sounds/sound-9______.mp3'))
		.then(() => playSound('https://www.myinstants.com/media/sounds/ps_1.mp3'))
		.then(() => playSound('https://www.myinstants.com/media/sounds/wrong-answer-sound-effect.mp3'))
		.then(() => playSound('https://www.myinstants.com/media/sounds/lalalalala.swf.mp3'))
		.then(() => playSound('https://www.myinstants.com/media/sounds/tuturu_1.mp3'))
		.then(() => playSound('https://www.myinstants.com/media/sounds/hallelujahshort.swf.mp3'))
		.then(repeatHeyListen);
}

async function addTicketsToBasket(dayElement) {
	dayElement.click();

	await new Promise((resolve) => setTimeout(resolve, 2000));
	await waitForAvailability();

	const chooseTimeButton = $('.ui-control.button.select-time:not(.disabled)')[0];
	if (!chooseTimeButton) return false;

	console.log('Found tickets!!!!!');
	playSounds();

	chooseTimeButton.click();

	await new Promise((resolve) => setTimeout(resolve, 2000));

	$('.typcn.typcn-shopping-cart.ng-binding')[0].click();
	return true;
}

function checkForTickets(datesWanted=[4,5,6], adultTicketsWanted=4, checkFrequency=15, monthWanted=5, hourRange=9-8) {
	setAdultTickets(adultTicketsWanted);

	async function check() {
		try {
			if ($('.ui-control.button.extendSession').length != 0) {
				console.log('Extending session');
				$('.ui-control.button.extendSession').click();
			}
			
			$('.shared-calendar-button').click();

			await waitForAvailability(monthWanted)
				.then(async ({ availableEls, month }) => {
					console.log(new Date(), `Availability loaded${month != null ? ` for month ${month}` : ''}. Checking for relevant dates...`);

					if (monthWanted != null && month > monthWanted) {
						console.log(`Month is too late (${month}). Will check again in ${checkFrequency} seconds.`);
						setTimeout(check, checkFrequency * 1000);
						return;
					}

					for (let i = 0; i < availableEls.length; i++) {
						const day = parseInt($('.calendar>.row:not(.blankLoader) .calendar-body .day.available')[i].innerText, 10);
						console.log('Day', day, 'is available...');
						if (datesWanted.includes(day)) {
							const succeeded = await addTicketsToBasket($('.calendar>.row:not(.blankLoader) .calendar-body .day.available')[i], hourRange);
							if (succeeded) return;
						}
					}

					console.log(`Relevant dates not yet available. Will check again in ${checkFrequency} seconds.`);
					setTimeout(check, checkFrequency * 1000);
			});
		} catch (err) {
			console.error('Error checking. Just gonna keep trying.', err);
			setTimeout(check, checkFrequency * 1000);
		}
	};
	check();
}

function checkForTicketsInMonth(datesWanted, monthWanted, adultTicketsWanted, checkFrequency) {
	return checkForTickets(datesWanted, adultTicketsWanted, checkFrequency, monthWanted);
}

@AngshumanDey
Copy link

Any guide how can run this code to monitor the changes in ticket status and notify me?
Can I run it on phone?
Or do I need to host a webpage?

@wired14
Copy link

wired14 commented Mar 18, 2025

Any guide how can run this code to monitor the changes in ticket status and notify me? Can I run it on phone? Or do I need to host a webpage?

Open console on your browser that is under developer tools. Update fields in checkForTickets for dates you want. Paste code snippet in console. Then run “checkForTickets()”.

@Rovack
Copy link
Author

Rovack commented Mar 18, 2025

Yay, thanks for the update @wired14! Hope you have a great time. :)

And hi, @AngshumanDey.

Any guide how can run this code to monitor the changes in ticket status and notify me? Can I run it on phone? Or do I need to host a webpage?

I don't believe you can easily run it on a phone, but you don't need to host a webpage or anything either. On a computer, all you have to do is open the browser's Console, and there you can paste the code and run it.

You can see the basic instructions in the first comment here, but you might also want to scan through the comments posted since then, which may contain updates and solutions for different problems folks have run into.

@AngshumanDey
Copy link

@wired14 Thanks. It works now

@Rovack : The script is great. It worked for the test dates. My problem is now I have to keep my computer running until my desired dates are available. Not sure how long that is going to take sadly.

@wired14
Copy link

wired14 commented Mar 18, 2025

my

Great to hear. Looking at the dates that become available, a few tickets are more likely a month ahead of time, especially the couple days before. It took me 3 weeks of checking to get it. Just had to check in on the script once a day and make sure it was still running.

@AngshumanDey
Copy link

my

Great to hear. Looking at the dates that become available, a few tickets are more likely a month ahead of time, especially the couple days before. It took me 3 weeks of checking to get it. Just had to check in on the script once a day and make sure it was still running.

What do you mean when you say check it on the script once a day? I suppose you have a PC that is running 24x7 without going to stanby, right? I am not sure how to do that when I am at work.

@huoshenw
Copy link

huoshenw commented Jul 8, 2025

Hi, sometimes I run into this error that stops the script. Any way to optimize it so it keeps refreshing?

image

image

@Rovack
Copy link
Author

Rovack commented Jul 8, 2025

Hey @huoshenw. That looks like the same issue that @athko described back in 2023, but as far as I know it hasn't recurred in nearly 2 years... And as noted there, it appears to be an error in the code of the website itself, and not the script, which makes it all the more puzzling.
I suspect the reason it's stopping the script is that it's an unhandled error, so perhaps it's interrupting any running code including ours.

If so, it's possible that pasting something like the following in the Console before the script will, while not addressing the error itself, at least allow the script to continue refreshing and trying even after it happens (though I can't say for sure as I'm unable to reproduce the problem myself):

 window.addEventListener('error', function(event) {
    console.warn('Caught error:', event.error);
    event.preventDefault();
});

window.addEventListener('unhandledrejection', function(event) {
    console.warn('Caught unhandled rejection:', event.reason);
    event.preventDefault();
});

[Edit: Strikethroughs added since this turned out not to work.]

Beyond that, I just wonder why only some people seem to be running into this issue...
I'd think it's a timing issue (a "race condition"), but that wouldn't really explain why some folks have it happen quite frequently, whereas I've never encountered it even once.
I suppose it could be specific to some browser/OS/unusual set of circumstances - any chance that if you try a different browser it stops happening?

@huoshenw
Copy link

I tried this command but still got the same error. The quickest way to reproduce it is by purchasing through this website—issues pop up way more often than with regular ticket purchases, which should make it easier for you to pinpoint the problem
thanks again!!!!!

https://www.priceless.com/entertainment/product/191976/warner-bros-studio-tour-london-the-making-of-harry-potter/0/4007/options/location/9673/contentType/1_2_4_5/

@huoshenw
Copy link

Just a quick heads-up, my screenshot isn't complete—the full version I got looks like this, with no calendar showing up

image

@Rovack
Copy link
Author

Rovack commented Jul 11, 2025

@huoshenw Tried through the Pricessless link you shared, but I still can't get it to reproduce. I set the refresh rate to 1 second hoping it'd happen quickly, but even after a few hundred refreshes, I never ran into this issue.
Could you share what parameters you're using when running this (dates, etc)? Maybe they affect this somehow.

But either way, it occurs to me that one possible explanation for why the script stops is that the loading indicator doesn't clear properly when their code crashes, which means the script keeps waiting forever for loading to finish.
We can easily solve that by capping the amount of time it waits - the only problem is that I'm not sure that even if we detect these cases and try to reopen the dialog, the site will still be functioning properly once we do. Still, worth a shot.

Probably all you'd have to do is replace the waitForAvailability function with, say:

maxAttempts = 10;
function waitForAvailability(attempt = 1) {
	if (attempt > maxAttempts) return Promise.resolve([]);

	return new Promise((resolve) => {
		setTimeout(() => {
			const availableEls = $('.calendar>.row:not(.blankLoader) .calendar-body .day.available'), isLoading = $('.calendar-modal[data-component=eventTimeModal] .modal-content > .loading-mask.hide').length === 0;
			if (isLoading) {
				return waitForAvailability(attempt + 1)
					.then((res) => resolve(res));
			}

			resolve(availableEls);
		}, 1000);
	});
}

(If you're using the code version that allows you to select a month too it'd be slightly different; just let me know if so and I can send over a modified version of that as well.)

If this still doesn't work because the site just stops working whenever it runs into that error, then I suppose we'd have to either fix their code so it doesn't crash to begin with, figure out some way to hard-refresh the page while keeping the script running, or at least notify you loudly so you can manually refresh...
None of the options sounds that simple, so let's just hope this works, and see what we can do if not.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment