Skip to content

Instantly share code, notes, and snippets.

@Rovack
Last active October 5, 2025 23:35
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 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.

@paterson37
Copy link

Hi,

Not sure if I'm doing something wrong because I'm not really experienced in this.

I run the script in my console, press enter and then put the checkForTickets([8]) command and press enter. But the page does nothing and I get the below showing in the console.

Uncaught TypeError: Cannot read properties of null (reading '0')
at setAdultTickets (:2:71)
at checkForTickets (:63:2)
at :1:1

Do you know what I'm doing wrong here?

@Rovack
Copy link
Author

Rovack commented Oct 4, 2025

Hey @paterson37. I see they've added some sort of queue in front of the actual ticketing website, which says it's paused with >12k people in it - kinda sounds like the site is effectively down.
Is this the page where you're trying to run the script? If so, it makes sense you'd get that error, as the script is intended to run in the site where you actually book tickets, not this queue.

@paterson37
Copy link

Hey @paterson37. I see they've added some sort of queue in front of the actual ticketing website, which says it's paused with >12k people in it - kinda sounds like the site is effectively down. Is this the page where you're trying to run the script? If so, it makes sense you'd get that error, as the script is intended to run in the site where you actually book tickets, not this queue.

Ahh no, I hadn't been following that link.

I've been going via here that takes you straight into the booking portal.

https://book.wbstudiotour.com/tickets

@Rovack
Copy link
Author

Rovack commented Oct 4, 2025

Ah, I see. If I'm not mistaken, that site is actually for the Hollywood WB Studio Tour, whereas this script is for the London one.
If you actually meant the London tour, you'd need to use this link, and the script should work.
If you really did mean the Hollywood one, then unfortunately the sites are built quite differently and the script won't work for that one without significant modifications. :(

However, nowadays writing your own script might not be too difficult, even without prior coding experience!
AI/"vibe-coding" tools like Claude Code may be able to do it quite easily. It might not even be entirely out of the realm of possibility for plain ChatGPT to do it - might be worth a shot.
If it doesn't succeed straight away, you could even try copying this script into it for reference, explaining that you want similar logic but for that different website.

@paterson37
Copy link

Thanks for getting back to me @Rovack

It's the London one I'm trying. When I click on "Buy Tickets" it re-directs me to the .com webpage I linked above even though I'm UK based. The link in your reply just now brings me to some sort of error page.

image

@Rovack
Copy link
Author

Rovack commented Oct 4, 2025

Hmm, very strange - never seen that sort of error page. Clicking the link in my earlier comment takes me to the proper booking page:
image

Perhaps copy-pasting it to the browser's address bar will work better? If so, it's:
https://tickets.wbstudiotour.co.uk/webstore/shop/viewitems.aspx

If that still doesn't work, I can only guess maybe something in your browser isn't working well with that site.
I'd try perhaps opening it in incognito mode, using a different browser altogether, or trying later.

Let me know if none of those work, and maybe we can figure out how to debug this further.

@paterson37
Copy link

How weird. I've tried it on different browsers, incognito/private windows and different networks and getting the same.

Will try again in the morning and update.

@Rovack
Copy link
Author

Rovack commented Oct 5, 2025

Hey @paterson37. It occurred to me this morning to also try in incognito, and I do see the same error you shared.
While I haven't looked into why it's happening, it does look like this link works even then (which is the same, just with a couple of params that they auto-add for me but I guess not consistently).
Perhaps with this one the page will load for you too?

@paterson37
Copy link

@Rovack That URL is working. Thanks!

However got a little bit of a problem with the script. This is probably user error.

I'm using this script.

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&gt;.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);
}

and the below command

checkForTicketsInMonth([9], 10, 2, 3, { minHour: 0, maxHour: 11 })

It seems to work to a point. But it won't refresh or recheck and I get the below in the console.

image

I'm sure it will be user error.

@Rovack
Copy link
Author

Rovack commented Oct 5, 2025

It looks like the code you're running isn't exactly what you pasted in the comment, since the version in your comment doesn't include the words "Tickets found at wanted date" anywhere but the screenshot does.

I imagine you probably also made the modifications suggested here?
If so, perhaps one of the steps there wasn't completed. Note that there are 3 short sections that need to change in addition to the big function that's replaced.

That's my best guess, since when I copy the code from your comment, make those changes, and run:

checkForTicketsInMonth([9], 10, 2, 3, { minHour: 0, maxHour: 11 })

it seems to work just fine - keeps refreshing and trying, and I don't get any errors.

In case it helps, the full version I get with those changes is:

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, { 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() {
		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, hourRange) {
	return checkForTickets(datesWanted, adultTicketsWanted, checkFrequency, monthWanted, hourRange);
}

@paterson37
Copy link

@Rovack That's it working now! Thank you very much. Here's hoping early morning tickets become available :D

@Rovack
Copy link
Author

Rovack commented Oct 5, 2025

Great to hear it! I certainly hope so too. :)

@wingpan79
Copy link

@Rovack Thank you very much!!!!! It works perfectly without modifying anything. Now I wait until I get the tickets I need.

@wingpan79
Copy link

@Rovack I noticed that when all the tickets for October are sold out, the script automatically jumps to the same date in November (for example, if I’m searching for the 24th)

@Rovack
Copy link
Author

Rovack commented Oct 5, 2025

IIRC that's just how the website behaves - skips to the month that has the nearest available tickets - but as long as the script recognizes that it's the wrong month and instead of adding tickets, just keeps refreshing and waiting, then it should be fine.

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