Skip to content

Instantly share code, notes, and snippets.

@sharethewisdom
Last active October 19, 2025 13:03
Show Gist options
  • Save sharethewisdom/a3a1a03b6b76d4935375ddb6dd6126d8 to your computer and use it in GitHub Desktop.
Save sharethewisdom/a3a1a03b6b76d4935375ddb6dd6126d8 to your computer and use it in GitHub Desktop.
Coderdojo Belgium: automatic registration of a single volunteer for the next event in a particular city through Eventbrite

Eventbrite Coderdojo Belgium volunteer registration

These simple userscripts automate registration for a single volunteer on the next dojo in a city.

If you're a volunteer coach just install and use it.

If you're a lead coach, and you want to register your volunteers, consider creating multiple firefox profiles and install the scripts in each of them to make this easier. Or, you could copy-paste the json of the volunteer's personal data prior to visiting the form. Set Tampermonkey config mode to advanced to see the json settings in the settings tab on the extension's editor page for the script eventbrite-checkout-coach.

If you're a parent or guardian, or an attendee, and you want to use this, it sould not be too hard to adapt this. You might want to just remove the "ticket-quantity-selector" if statement in eventbrite-checkout-coach to manually select a track, and modify it to add additional data if the attendee data is different from the person making the reservation. The timeconsuming part would be to be able to add multiple attendees in one go. Selecting elements with random id's and no other discernible features buried in layers of nested div's is difficult. Plus, there might be some React trickery going on.

installation

  • install Tampermonkey on Firefox (which allows scripts to run at document-start contrary to most other browsers)
  • install the three *.user.js scripts by clicking the raw button
  • bookmark a city filter query url for your city like https://coderdojobelgium.be/nl/dojos?city=maldegem

usage

first time

  • browse to your bookmarked Coderdojo Belgium dojos page
  • wait for the prompts on the checkout form (comment on this gist if you have problems)
  • fill in your personal details once (this is stored alongside the userscript)
  • once you verified that it works, uncomment the line below // uncomment to actually register

future usage

Just browse to your bookmark. There might be an additional question after registration. I'm not yet sure if registration is complete when you skip this. Anyway this is not automated yet.

how?

  • the first script waits for, and clicks, the first CoderDojo register button on coderdojobelgium.be
  • the second script waits for, and clicks the Eventbrite registeration form button and redirects to the form's iframe url when that appears
  • the third script runs on the Eventbrite registeration checkout form:
    • first you're prompted for missing personal data to be stored
    • a ticket on the first select element in the document is chosen as it matches a coach ticket for Maldegem Dojos (you might need to adapt this if it is a hidden ticket or if it is not the first element!)
    • when React updates the #order-summary-container a recursive function waitForReactOnClickEventHandler is called until the register button's onlick handler exists, and then the button is clicked
    • then, it waits for a <section>; This appeared to me the only way to detect when the second checkout form loaded which is conspicuous to me.
    • a bunch of wrapper functions are then used to use the native value setters on input and select elements and trigger the React hooks
    • if you uncommented the waitForReactOnClickEventHandler in filloutForm(), and the form validates correctly, you'll be taken to additional questions after registration.

Why?

Eventbrite is in my opinion a very slow React piece of garbadge which has annoying features which are never needed for free of charge events like Coderdojo. On the other hand, event organizer features for usability are missing. Simple crucial things like finding and printing a list of registrations is even hard. Hidden tickets are hidden for everyone. You can't make them visible for select accounts: so the lead must register the volunteer. Why Eventbrite, why?

In summary:

  • It's cumbersome for lead coaches
  • Coderdojo asks too many questions, surely nobody likes marketing surveys
  • it is unnecessarily slow to actually register to the event every month, the console is flooded with CORS errors, ...

Manually creating events and repeated registration however can't be solved with recurring events because these can't be integrated easily in Coderdojo Belgium's Drupal website, and also because events might not be as recurrent for every location.

Oh did I mention that it is extremely slow and annoying?

A marginally better soluton.

We could orchestrate automatic login of a lead coach using Selinium in a VPS with the undetected-chromedriver Python package to prevent detection by Eventbrite, and a bunch of scripts triggered with webhooks to automate stuff. One script for making events from a template event could be useful, maybe based on a spreadsheet, because the Eventbrite API does not allow for this. Also there should be an easy way for lead coaches to bulk add a number of attendees in particular tracks on the next event. This should ease the pain for regular attendee's parents/guardians, but then it's up to the lead coach to determine when to do so, at risk of being fully booked.

A better solution.

Get rid of Eventbrite as a requirement. Coderdojo Belgium may be fun and free of charge, but we don't really carry out the "free" as in freedom as much as I'd like. Self-host an open source ticketing system geared toward small volunteering communities. Also prepare for self-hosting online events.

// ==UserScript==
// @name coderdojobelgium-upcoming-dojo-in-city-picker
// @namespace http://tampermonkey.net/
// @version 2025-10-10
// @description click the first CoderDojo register button
// @author You
// @match https://coderdojobelgium.be/*/dojos?city=*
// @icon https://www.google.com/s2/favicons?sz=64&domain=coderdojobelgium.be
// @grant GM_addStyle
// @run-at document-start
// ==/UserScript==
(function() {
'use strict';
GM_addStyle(`
.page__intro,.dojos__map {
display: none !important;
}
:focus {
border: 3px solid red;
}
`);
const childObserver = new MutationObserver((mutationList,observer) => {
for (const mutation of mutationList) {
for (const node of mutation.addedNodes.values()) {
if (node.nodeName.toLowerCase() === "a" && node.classList.contains('button')) {
observer.disconnect()
setTimeout(() => {document.location.href = document.querySelector("a.button").href}, 1000)
}
}
}
})
childObserver.observe(document.documentElement, { childList: true, subtree: true });
})();
// ==UserScript==
// @name eventbrite-checkout-coach
// @namespace http://tampermonkey.net/
// @version 2025-10-10
// @description The CoderDojo Eventbrite registeration form is autofilled answering questions and
// @author You
// @match https://www.eventbrite.co.uk/checkout-external*
// @icon https://www.google.com/s2/favicons?sz=64&domain=eventbrite.co.uk
// @grant GM_addStyle
// @grant GM_setValue
// @grant GM_getValues
// @run-at document-start
// ==/UserScript==
(function() {
'use strict';
GM_addStyle(`
html,img{
filter: hue-rotate(180deg) invert(100) !important;
}
.eds-modal__body {
max-height: none!important;
max-width: none!important;
}
:focus {
border: 3px solid red;
}`);
const year = new Date().getFullYear();
// eslint-disable-next-line
let data = GM_getValues({
firstname: null,
lastname: null,
email: null,
mobilephone: null,
address: null,
city: null,
postal: null,
sex: null,
birthyear: null
});
const askForSex = () => { // come on... it's not that funny
let sex = "";
while (sex != "m" && sex != "v" && sex != "a") {
sex = prompt('Sex male, female, or other? (m/f/o):');
}
GM_setValue("sex", sex)
}
Object.keys(data).forEach((k) => {
if (data[k] == null) {
if (k == "sex") {
askForSex()
} else {
// eslint-disable-next-line
GM_setValue(k, prompt("Please enter the " + k + ":"))
}
} else {
if (k == "sex") {
const sex = data[k]
//if (sex != "m" && sex != "f" && sex != "o") askForSex();
} else if (k == "postal" || k == "birthyear") {
if (!Number.isInteger(parseInt(data[k])) || data[k].length != 4) {
GM_setValue(k, prompt(data[k].length + " is wrong.\r\nPlease enter the " + k + ":"))
}
} else if (k == "mobilephone") {
if (!Number.isInteger(parseInt(data[k])) || (data[k].length != 9 && data[k].length != 10)) {
GM_setValue(k, prompt('"' + data[k].length + '" is wrong (expected 9 or 10 digits).\r\nPlease enter the ' + k + ":"))
}
}
}
})
// eslint-disable-next-line
data = GM_getValues([
"firstname",
"lastname",
"email",
"mobilephone",
"address",
"city",
"postal",
"sex",
"birthyear"
])
const waitForReactOnClickEventHandler = (callback, selector) => {
const el = document.querySelector(selector)
const reactEv = Object.getOwnPropertyNames(el).find((p) => p.startsWith("__reactEventHandlers"))
if (typeof el[reactEv].onClick == "undefined") {
setTimeout(waitForReactOnClickEventHandler.bind(null,callback,selector),10)
} else {
setTimeout(callback.bind(null, el), 1000)
}
}
const setReactSelectValue = (el, value) => {
const last = el.value; // Store the last value to update React's internal tracker
const nativeSelectValueSetter = Object.getOwnPropertyDescriptor(
window.HTMLSelectElement.prototype, 'value'
).set;
nativeSelectValueSetter.call(el, value);
// Update React's internal value tracker
const tracker = el._valueTracker;
if (tracker) {
tracker.setValue(last);
}
// Dispatch the input event to notify React of the change
el.dispatchEvent(new Event('change', { bubbles: true }));
el.dispatchEvent(new Event('input', { bubbles: true }));
}
const setReactInputValue = (el, value) => {
const last = el.value; // Store the last value to update React's internal tracker
const nativeInputValueSetter = Object.getOwnPropertyDescriptor(
window.HTMLInputElement.prototype, 'value'
).set;
nativeInputValueSetter.call(el, value);
// Update React's internal value tracker
const tracker = el._valueTracker;
if (tracker) {
tracker.setValue(last);
}
// Dispatch the input event to notify React of the change
el.dispatchEvent(new Event('input', { bubbles: true }));
}
const filloutForm = () => {
document.documentElement.style.scrollBehavior = "smooth";
const bName = document.getElementById('buyer.N-first_name')
const bLastName = document.getElementById('buyer.N-last_name')
const bEmail = document.getElementById('buyer.N-email')
const bEmailConfirm = document.getElementById('buyer.confirmEmailAddress')
const attendee = document.querySelector('.responsive-form > div > div:not(#buyer)')
const firstName = attendee.querySelector('input[id$="first_name"]')
const lastName = attendee.querySelector('input[id$="last_name"]')
const email = attendee.querySelector('input[id$="email"]')
const phone = attendee.querySelector('input[id$="cell_phone"]')
const address = document.getElementById("N-homeaddress1")
const city = document.getElementById("N-homecity")
const postal = document.getElementById("N-homepostal")
let sex;
if (data.sex == "m") {
sex = attendee.querySelector('input[value="Male"]');
} else if (data.sex == "f") {
sex = attendee.querySelector('input[value="Female"]');
} else {
sex = attendee.querySelector('input[value="Other"]');
}
// const age = attendee.querySelector('input[id$="304639153"]')
// const laptop = attendee.querySelector('select[id$="304639163"]')
//const consentPic = attendee.querySelector('input[value="1628038343"]')
//const consentData = attendee.querySelector('input[value="1628038373"]')
const cells = attendee.querySelectorAll('.eds-g-cell:has(input[value="Other"]) ~ div.eds-g-cell')
const age = cells[0].getElementsByTagName('input')[0]
const laptop = cells[1].getElementsByTagName('select')[0]
const nee = laptop.options[2].value
const consentPic = cells[2].getElementsByTagName('input')[0]
const consentData = cells[3].getElementsByTagName('input')[1]
if (bName.value == "") setReactInputValue(bName,data.firstname)
if (bLastName.value == "") setReactInputValue(bLastName,data.lastname)
if (bEmail.value == "") setReactInputValue(bEmail,data.email)
if (bEmailConfirm) setReactInputValue(bEmailConfirm,data.email)
if (firstName.value == "") setReactInputValue(firstName,data.firstname)
if (lastName.value == "") setReactInputValue(lastName,data.lastname)
if (email.value == "") setReactInputValue(email,data.email)
setReactInputValue(phone, data.mobilephone)
address.focus()
setReactInputValue(address, data.address);
setReactInputValue(city, data.city);
setReactInputValue(postal, data.postal);
setReactInputValue(age, year-data.birthyear);
sex.click() // maybe use waitForReactOnClickEventHandler?
setReactSelectValue(laptop, nee)
consentPic.click() // maybe use waitForReactOnClickEventHandler?
consentData.scrollIntoView({block: 'end'})
consentData.click()
consentData.focus()
// uncomment to actually register
//waitForReactOnClickEventHandler((el) => el.click(),'main button.eds-btn')
}
//const pane = document.getElementById("order-summary").childNodes[2];
//pane.textContent = "ok";
const childObserver = new MutationObserver((mutationList,observer) => {
for (const mutation of mutationList) {
for (const node of mutation.addedNodes.values()) {
const tag = node.nodeName.toLowerCase()
if (tag === "div") {
if (node.dataset.testid === "ticket-quantity-selector") {
//const q = node.getElementsByTagName('select')[0];
// I only want the first in the document
// [0] Coderdojo coach
// [1] Eigen traject
const q = document.getElementsByTagName('select')[0];
//const q = document.getElementById("ticket-quantity-selector-2710912943");
setReactSelectValue(q, "1") // one ticket
}
if (node.id === "order-summary-container") {
waitForReactOnClickEventHandler((el) => el.click(),'main button.eds-btn')
}
if (node.dataset.spec === "form-instructions") {
node.style.display = "none";
}
} else if (tag === "section") {
const note = document.querySelector('div[data-spec="form-instructions"]')
note.style.display = "none";
filloutForm()
observer.disconnect()
}
}
}
}).observe(document.documentElement, { childList: true, subtree: true });
})();
// ==UserScript==
// @name eventbrite-register-redirect
// @namespace http://tampermonkey.net/
// @version 2025-10-10
// @description clicks the Eventbrite registeration form button and redirects to the form's iframe url
// @author You
// @match https://www.eventbrite.co.uk/e/registratie-coderdojo-*
// @icon https://www.google.com/s2/favicons?sz=64&domain=eventbrite.co.uk
// @grant GM_addStyle
// @run-at document-start
// @noframes
// ==/UserScript==
(function() {
'use strict';
GM_addStyle(`
html,img{
filter: hue-rotate(180deg) invert(100) !important;
}
.event-hero-wrapper {
display:none;
}
:focus {
border: 3px solid red;
}`)
const childObserver = new MutationObserver((mutationList,observer) => {
for (const mutation of mutationList) {
for (const node of mutation.addedNodes.values()) {
const tag = node.nodeName.toLowerCase()
if (tag === "button" && node.textContent === "Reserveer een plek") { // TODO multilingual
node.click()
}
if (tag === "iframe") {
// maybe speed things up by preventing loading the iframe page?
// @unsafeWindow object provides access to the window object
// of the page that Tampermonkey is running on
// window.frames[0].stop()
observer.disconnect()
document.location.href = node.src
}
}
}
})
childObserver.observe(document.documentElement, { childList: true, subtree: true });
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment