user script for re:Invent 2025 event portal to improve its calendar by:
- show code, type and venue on the calendar directly
- highlight sessions where isn't a breakout session and you have no reserved seat
Confirmed working with Tampermonkey.
| // ==UserScript== | |
| // @name reinvent2025 calendar show session data | |
| // @namespace http://tampermonkey.net/ | |
| // @version 2025-12-01 | |
| // @author sorah | |
| // @match https://registration.awsevents.com/flow/awsevents/reinvent2025/calendar/page/calendar | |
| // @icon https://www.google.com/s2/favicons?sz=64&domain=awsevents.com | |
| // @grant none | |
| // ==/UserScript== | |
| (function () { | |
| "use strict"; | |
| let myData = null; | |
| let sessions = null; | |
| const getSessions = (data) => { | |
| const newSessions = []; | |
| const mapSession = (s) => { | |
| if (!s.times) { | |
| console.warn("no times", s); | |
| return null; | |
| } | |
| if (s.times?.length > 1) console.warn("multiple times", s); | |
| const time = s.times[0]; | |
| const retval = { | |
| sessionID: s.sessionID, | |
| code: s.code, | |
| title: s.title, | |
| time, | |
| date: time.daySort, | |
| startTime: time.startTimeFormatted.replace(/^0(\d):/, "$1:"), | |
| endTime: time.endTimeFormatted.replace(/^0(\d):/, "$1:"), | |
| type: s.attributevalues?.find((a) => a.attribute == "Type")?.value, | |
| venue: s.attributevalues?.find((a) => a.attribute == "Venue")?.value, | |
| }; | |
| retval.matcher = new RegExp( | |
| `^${retval.startTime} - ${retval.endTime}[^:]+: ${RegExp.escape( | |
| retval.title | |
| )}` | |
| ); | |
| return retval; | |
| }; | |
| data.mySchedule?.forEach((s) => { | |
| if (s.type !== "Enrolled") return; | |
| newSessions.push(mapSession(s)); | |
| }); | |
| data.sessionInterests?.forEach((s) => { | |
| newSessions.push(mapSession(s)); | |
| }); | |
| return newSessions.filter((s) => s); | |
| }; | |
| const startObserver = () => { | |
| const action = (session, el) => { | |
| const sub = | |
| el.querySelector(".srh-session-meta") || document.createElement("div"); | |
| sub.innerHTML = ""; | |
| sub.classList.add("srh-session-meta"); | |
| [session.code, session.type, `@ ${session.venue}`].forEach((t) => { | |
| const p = document.createElement("p"); | |
| p.appendChild(document.createTextNode(t)); | |
| sub.appendChild(p); | |
| }); | |
| el.querySelector(".rbc-event-content")?.appendChild(sub); | |
| if (!el.classList.contains("enrolled")) { | |
| if (session.type !== "Breakout session") { | |
| el.style.backgroundColor = "#6f6191"; | |
| } else { | |
| el.style.backgroundColor = null; | |
| } | |
| } else { | |
| el.style.backgroundColor = null; | |
| } | |
| }; | |
| const remove = (el) => { | |
| el.querySelectorAll(".srh-session-meta").forEach((e) => e.remove()); | |
| el.style.backgroundColor = null; | |
| }; | |
| const check = (el) => { | |
| if (!sessions) return; | |
| if (!el.classList.contains("schedule-calendar-session")) { | |
| remove(el); | |
| return; | |
| } | |
| const daySlot = el.closest(".rbc-day-slot"); | |
| if (!daySlot) return; | |
| const m = daySlot | |
| .querySelector("button.rbc-time-slot") | |
| ?.dataset.test?.match(/\d{4}\d{2}\d{2}/); | |
| if (!m) return; | |
| const date = m[0]; | |
| const session = sessions.find((s) => { | |
| const mt = s.matcher.test(el.title); | |
| const md = s.date === date; | |
| return mt && md; | |
| }); | |
| if (!session) { | |
| console.warn({ date, el }); | |
| remove(el); | |
| return; | |
| } | |
| action(session, el); | |
| }; | |
| const observer2 = new window.MutationObserver(function (set) { | |
| set.forEach((mutation) => { | |
| if (mutation.target.nodeType == 3) { | |
| const node = mutation.target.parentElement; | |
| const sessionEl = node.classList.contains("schedule-calendar-session") | |
| ? node | |
| : node.closest(".schedule-calendar-session"); | |
| if (sessionEl) check(sessionEl); | |
| } | |
| }); | |
| }); | |
| const observer = new window.MutationObserver(function (set) { | |
| set.forEach((mutation) => { | |
| mutation.addedNodes.forEach((node) => { | |
| if (node.nodeType == 1) { | |
| const sessionEls = node.classList.contains( | |
| "schedule-calendar-session" | |
| ) | |
| ? [node] | |
| : Array.from(node.querySelectorAll(".schedule-calendar-session")); | |
| sessionEls.forEach((el) => { | |
| check(el); | |
| observer2.observe(el, { | |
| childList: true, | |
| subtree: true, | |
| characterData: true, | |
| }); | |
| }); | |
| } | |
| }); | |
| }); | |
| }); | |
| observer.observe(document.body, { childList: true, subtree: true }); | |
| document | |
| .querySelectorAll(".schedule-calendar-session") | |
| .forEach((x) => check(x)); | |
| }; | |
| const getMyData = () => { | |
| const authToken = window.authToken; | |
| const apiProfileId = window.widget?.apiToken; | |
| const widgetId = window.widget?.widgetId; | |
| const domain = window.vanityDomains?.events; | |
| if (!authToken || !apiProfileId || !widgetId || !domain) { | |
| setTimeout(getMyData, 500); | |
| return; | |
| } | |
| fetch(`https://${domain}/api/myData`, { | |
| method: "post", | |
| body: null, | |
| credentials: "include", | |
| headers: { | |
| rfapiprofileid: apiProfileId, | |
| rfauthtoken: authToken, | |
| rfwidgetid: widgetId, | |
| }, | |
| }) | |
| .then((r) => r.json()) | |
| .then((j) => { | |
| myData = j; | |
| sessions = getSessions(j); | |
| startObserver(); | |
| }); | |
| }; | |
| setTimeout(getMyData, 500); | |
| })(); |