Skip to content

Instantly share code, notes, and snippets.

@cjshearer
Last active October 12, 2025 17:30
Show Gist options
  • Save cjshearer/6474e1917c1cfa9ea5d65062e9226352 to your computer and use it in GitHub Desktop.
Save cjshearer/6474e1917c1cfa9ea5d65062e9226352 to your computer and use it in GitHub Desktop.
ATO25 to Markdown
// Made with GPT 4.1
// Run this in the console at https://2025.allthingsopen.org/schedule?dy=monday
// to get the full conference schedule as markdown.
//
// Note that this does scrap descriptions for each talk's page, so do be kind to the website
// and don't run this unnecessarily.
// 1. Load Turndown if not already loaded
if (typeof TurndownService === 'undefined') {
await new Promise(resolve => {
const script = document.createElement('script');
script.src = 'https://unpkg.com/turndown/dist/turndown.js';
script.onload = resolve;
document.head.appendChild(script);
});
}
// 2. Main extraction function
async function extractScheduleWithDescriptions() {
function formatDate(dateStr) {
const months = {
January: "01", February: "02", March: "03", April: "04",
May: "05", June: "06", July: "07", August: "08",
September: "09", October: "10", November: "11", December: "12"
};
const [month, day, year] = dateStr.replace(',', '').split(' ');
return `${year}-${months[month]}-${day.padStart(2, '0')}`;
}
const dateSpan = document.querySelector('.col.ar.c3 span');
const date = formatDate(dateSpan.textContent.trim());
let md = `## ${date}\n\n`;
const turndownService = new TurndownService();
async function fetchDescription(url) {
try {
const resp = await fetch(url);
const html = await resp.text();
const parser = new DOMParser();
const doc = parser.parseFromString(html, "text/html");
const frame = doc.querySelector('.page-frame.session-single');
if (!frame) return '';
const titleEl = frame.querySelector('.page-title');
const speakersHeader = frame.querySelector('.session-speakers-header');
if (!titleEl || !speakersHeader) return '';
let htmlDesc = '';
let node = titleEl.nextElementSibling;
while (node && node !== speakersHeader) {
htmlDesc += node.outerHTML || '';
node = node.nextElementSibling;
}
return turndownService.turndown(htmlDesc).trim();
} catch (e) {
return '';
}
}
const timeBlocks = Array.from(document.querySelectorAll('.time-block'));
for (const timeBlock of timeBlocks) {
const rangeDiv = timeBlock.querySelector('.time-block-range');
if (!rangeDiv) continue;
const timeRange = rangeDiv.childNodes[0].textContent.trim();
const timezone = rangeDiv.querySelector('small')?.textContent.trim() || '';
md += `### ${timeRange} ${timezone}\n\n`;
const sessions = Array.from(timeBlock.querySelectorAll('.time-block-session-item'));
for (const session of sessions) {
const cat = session.querySelector('.time-block-session-item-cat')?.textContent.trim() || '';
const linkEl = session.querySelector('h5 a');
const title = linkEl?.textContent.trim() || '';
const link = linkEl?.href || '';
const speaker = session.querySelector('.time-block-session-item-speaker span')?.textContent.trim() || '';
let description = '';
if (link) {
description = await fetchDescription(link);
}
md += `#### ${title}\n\n`;
md += `category: ${cat} \n`;
md += `link: ${link} \n`;
if (speaker){
md += `speaker: ${speaker}\n`;
}
if (description) {
md += `\n${description}\n`;
}
md += `\n`;
}
}
return md;
}
// 3. Run the extraction and print the result
(async () => {
const markdown = await extractScheduleWithDescriptions();
console.log(markdown);
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment