115 lines
No EOL
4.7 KiB
JavaScript
115 lines
No EOL
4.7 KiB
JavaScript
// Usage: Replace the 4 constants below, then run `await generateMarkdownTable()` to create markdown
|
|
// Requires the event to be an `event series`; `39c3ht` is not so this will fail: https://docs.pretix.eu/guides/event-series/?h=dates#how-to-create-an-event-series
|
|
const token = ""; // generate using https://docs.pretix.eu/dev/api/tokenauth.html#obtaining-an-api-token
|
|
const baseUrl = 'https://tickets.hamburg.ccc.de/api/v2';
|
|
const organizer = "hackertours"; // example extracted from https://tickets.hamburg.ccc.de/hackertours/39c3ht
|
|
const eventName = "39c3ht"; // example extracted from https://tickets.hamburg.ccc.de/hackertours/39c3ht
|
|
|
|
async function generateMarkdownTable() {
|
|
async function fetchAllSubevents() {
|
|
const results = [];
|
|
let url = `${baseUrl}/organizers/${organizer}/events/${eventName}/subevents/`;
|
|
while (url) {
|
|
const resp = await fetch(url, {
|
|
headers: {
|
|
'Authorization': `Token ${token}`,
|
|
'Accept': 'application/json'
|
|
}
|
|
});
|
|
if (!resp.ok) {
|
|
throw new Error(`Failed to fetch subevents: ${resp.status} ${await resp.text()}`);
|
|
}
|
|
const json = await resp.json();
|
|
results.push(...json.results);
|
|
url = json.next;
|
|
}
|
|
return results;
|
|
}
|
|
|
|
function slugify(name) {
|
|
return name
|
|
.toLowerCase()
|
|
.replace(/[“”"']/g, '')
|
|
.replace(/\(.*?\)/g, '')
|
|
.replace(/&/g, 'and')
|
|
.replace(/[^a-z0-9]+/g, '-')
|
|
.replace(/^-+|-+$/g, '');
|
|
}
|
|
|
|
function langTags(title) {
|
|
const tags = [];
|
|
const hasGerman = /\(german\)|\bgerman\b|[äöüß]|stadtrundgang|rote flora|gängeviertel/.test(title);
|
|
const hasEnglish = /\(english\)|\benglish\b/.test(title);
|
|
const bilingual = /\b(bilingual)\b/.test(title);
|
|
if (bilingual) {
|
|
tags.push('{{< lang de >}}', '{{< lang en >}}');
|
|
} else {
|
|
if (hasGerman) tags.push('{{< lang de >}}');
|
|
if (hasEnglish) tags.push('{{< lang en >}}');
|
|
}
|
|
return tags.join(' ');
|
|
}
|
|
|
|
function formatDay(date) {
|
|
return date.toLocaleDateString('de-DE', { day: '2-digit', month: '2-digit' });
|
|
}
|
|
|
|
const subevents = await fetchAllSubevents();
|
|
if (!subevents.length) return '| Zeit | | |\n|------|---|---|';
|
|
|
|
const dayMap = new Map();
|
|
for (const s of subevents) {
|
|
if (!s.date_from) continue;
|
|
const d = new Date(s.date_from);
|
|
const dayKey = d.toISOString().slice(0, 10);
|
|
if (!dayMap.has(dayKey)) {
|
|
dayMap.set(dayKey, { dateObj: d, events: [] });
|
|
}
|
|
const hour = d.getUTCHours();
|
|
const minute = d.getUTCMinutes();
|
|
let title = ((s.name && (s.name.en || Object.values(s.name)[0])) || 'Untitled').replace(/^.*?-\s*/, '');
|
|
const slug = slugify(title);
|
|
const tags = langTags(title);
|
|
title = title.replace("(German)", "").replace("(English)", "").replace("(Bilingual)", "");
|
|
const md = `${hour.toString().padStart(2, '0')}:${minute.toString().padStart(2, '0')} [${title}](./${slug}/)${tags ? ' ' + tags : ''}`;
|
|
dayMap.get(dayKey).events.push({ hour, minute, md });
|
|
}
|
|
|
|
const days = Array.from(dayMap.entries()).sort((a, b) => a[1].dateObj - b[1].dateObj);
|
|
|
|
const hourSet = new Set();
|
|
for (const [, { events }] of days) {
|
|
for (const e of events) hourSet.add(e.hour);
|
|
}
|
|
const hoursSorted = Array.from(hourSet).sort((a, b) => a - b);
|
|
|
|
const baseDate = new Date('2025-12-26T00:00:00Z');
|
|
const headerCells = days.map(([key, val]) => {
|
|
const dayNumber = Math.floor((val.dateObj - baseDate) / (1000 * 60 * 60 * 24));
|
|
return `Tag ${dayNumber} (${formatDay(val.dateObj)})`;
|
|
});
|
|
|
|
let md = '| Zeit | ' + headerCells.join(' | ') + ' |\n';
|
|
md += '|------|' + headerCells.map(() => '---').join('|') + '|\n';
|
|
|
|
for (const hour of hoursSorted) {
|
|
const hourLabel = `${hour.toString().padStart(2, '0')}:00`;
|
|
|
|
const perDay = days.map(([, { events }]) =>
|
|
events
|
|
.filter(e => e.hour === hour)
|
|
.sort((a, b) => a.minute - b.minute)
|
|
);
|
|
|
|
const maxRows = Math.max(0, ...perDay.map(list => list.length));
|
|
if (maxRows === 0) continue;
|
|
|
|
for (let i = 0; i < maxRows; i++) {
|
|
const rowCells = perDay.map(list => list[i] ? list[i].md : '-');
|
|
const zeit = i === 0 ? hourLabel : '';
|
|
md += `| ${zeit} | ${rowCells.join(' | ')} |\n`;
|
|
}
|
|
}
|
|
|
|
return md;
|
|
} |