Initial commit: Padel Planner API
- Express API server met SQLite - Database migratie (migrate.cjs) - REST endpoints voor players, matches, scores - CHANGELOG.md en .gitignore
This commit is contained in:
@@ -0,0 +1,97 @@
|
||||
#!/usr/bin/env node
|
||||
// Peakz Padel beschikbaarheid scraper
|
||||
// Aanroep: node padel-peakz.mjs [datum] [locatie]
|
||||
// Voorbeeld: node padel-peakz.mjs 2026-05-24 Atoomweg
|
||||
|
||||
const { chromium } = require('playwright');
|
||||
|
||||
const LOCATIONS = {
|
||||
'Atoomweg': '6637dd8e-cd4b-4fee-af49-196f6828b7dc',
|
||||
'Groningen': '6637dd8e-cd4b-4fee-af49-196f6828b7dc',
|
||||
};
|
||||
const RESERVATION_TYPE = '6';
|
||||
const PLAYING_TIME = '90';
|
||||
|
||||
async function getAvailability(dateStr, locationName) {
|
||||
const locationId = LOCATIONS[locationName] || LOCATIONS['Atoomweg'];
|
||||
const browser = await chromium.launch({ headless: true });
|
||||
const context = await browser.newContext({
|
||||
userAgent: 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36'
|
||||
});
|
||||
const page = await context.newPage();
|
||||
|
||||
try {
|
||||
const url = `https://www.peakzpadel.nl/reserveren/court-booking/reservation?daypart=---&date=${dateStr}&location=${encodeURIComponent(locationName)}&playingTimes=${PLAYING_TIME}&courtTypeIds=13`;
|
||||
await page.goto(url, { waitUntil: 'networkidle', timeout: 30000 });
|
||||
await page.waitForTimeout(3000);
|
||||
|
||||
const slots = await page.evaluate(() => {
|
||||
const buttons = document.querySelectorAll('button');
|
||||
const results = [];
|
||||
for (const btn of buttons) {
|
||||
const text = btn.textContent.trim();
|
||||
if (text.match(/^\d{2}:\d{2}/)) {
|
||||
const price = text.match(/€\s*([\d,]+)/);
|
||||
results.push({
|
||||
time: text.split(' ')[0],
|
||||
price: price ? price[1] : null,
|
||||
available: !btn.disabled
|
||||
});
|
||||
}
|
||||
}
|
||||
return results;
|
||||
});
|
||||
|
||||
return {
|
||||
date: dateStr,
|
||||
location: locationName,
|
||||
slots,
|
||||
available: slots.filter(s => s.available),
|
||||
unavailable: slots.filter(s => !s.available)
|
||||
};
|
||||
} catch (err) {
|
||||
return { error: err.message };
|
||||
} finally {
|
||||
await browser.close();
|
||||
}
|
||||
}
|
||||
|
||||
// CLI mode
|
||||
const dateArg = process.argv[2] || new Date().toISOString().split('T')[0];
|
||||
const locationArg = process.argv[3] || 'Atoomweg';
|
||||
|
||||
getAvailability(dateArg, locationArg).then(result => {
|
||||
if (result.error) {
|
||||
console.error('Fout:', result.error);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Output als JSON voor machine-readable
|
||||
if (process.argv.includes('--json')) {
|
||||
console.log(JSON.stringify(result));
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
// Human readable
|
||||
const dayNames = ['zo', 'ma', 'di', 'wo', 'do', 'vr', 'za'];
|
||||
const d = new Date(result.date);
|
||||
const dayLabel = dayNames[d.getDay()];
|
||||
const dateLabel = `${dayLabel} ${d.getDate()}/${d.getMonth()+1}`;
|
||||
|
||||
console.log(`\n🏓 Peakz Padel — ${result.location}`);
|
||||
console.log(`📅 ${dateLabel}`);
|
||||
console.log(`🟢 ${result.available.length} banen vrij`);
|
||||
console.log(`🔴 ${result.unavailable.length} banen bezet`);
|
||||
console.log('');
|
||||
|
||||
if (result.available.length > 0) {
|
||||
console.log('Vrije tijden:');
|
||||
for (const s of result.available) {
|
||||
const price = s.price ? ` — €${s.price}` : '';
|
||||
console.log(` 🟢 ${s.time}${price}`);
|
||||
}
|
||||
}
|
||||
|
||||
console.log('');
|
||||
process.exit(0);
|
||||
});
|
||||
Reference in New Issue
Block a user