75912faae1
- Telegram bot met beschikbaarheid, scores, matching - Peakz scraper voor Atoomweg/Euroborg/Suikerterrein - SQLite backend via API
98 lines
3.6 KiB
JavaScript
98 lines
3.6 KiB
JavaScript
const { chromium } = require('playwright');
|
|
|
|
const LOCATIONS = [
|
|
{ name: 'Atoomweg', city: 'Groningen', indoor: true, courtTypeIds: '13' },
|
|
{ name: 'Euroborg', city: 'Groningen', indoor: true, courtTypeIds: '5' },
|
|
{ name: 'Suikerterrein', city: 'Groningen', indoor: false, courtTypeIds: '' },
|
|
];
|
|
|
|
const PLAYING_TIME = '90';
|
|
|
|
function findLocation(nameOrAlias) {
|
|
const lower = nameOrAlias.toLowerCase();
|
|
for (const loc of LOCATIONS) {
|
|
if (loc.name.toLowerCase() === lower || loc.city.toLowerCase() === lower) return loc;
|
|
}
|
|
if (lower === 'ataomweg' || lower === 'atoom') return LOCATIONS[0];
|
|
if (lower === 'euro') return LOCATIONS[1];
|
|
if (lower === 'suiker') return LOCATIONS[2];
|
|
return null;
|
|
}
|
|
|
|
async function getAvailability(dateStr, locationName) {
|
|
const loc = findLocation(locationName) || LOCATIONS[0];
|
|
const browser = await chromium.launch({ headless: true, args: ['--no-sandbox', '--disable-gpu'], executablePath: '/usr/bin/chromium-browser' });
|
|
const context = await browser.newContext({ userAgent: 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36' });
|
|
const page = await context.newPage();
|
|
|
|
try {
|
|
// Bouw de URL met de juiste parameters per locatie
|
|
let url = `https://www.peakzpadel.nl/reserveren/court-booking/reservation?daypart=---&date=${encodeURIComponent(dateStr)}&location=${encodeURIComponent(loc.name)}&playingTimes=${PLAYING_TIME}`;
|
|
if (loc.courtTypeIds) {
|
|
url += '&courtTypeIds=' + loc.courtTypeIds;
|
|
}
|
|
|
|
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: loc.name,
|
|
indoor: loc.indoor,
|
|
slots,
|
|
available: slots.filter(s => s.available),
|
|
unavailable: slots.filter(s => !s.available)
|
|
};
|
|
} catch (err) {
|
|
return { error: err.message };
|
|
} finally {
|
|
await browser.close();
|
|
}
|
|
}
|
|
|
|
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); }
|
|
if (process.argv.includes('--json')) { console.log(JSON.stringify(result)); process.exit(0); }
|
|
|
|
const dayNames = ['zo', 'ma', 'di', 'wo', 'do', 'vr', 'za'];
|
|
const d = new Date(result.date);
|
|
const dayLabel = dayNames[d.getDay()] + ' ' + d.getDate() + '/' + (d.getMonth()+1);
|
|
const typeIcon = result.indoor ? '🏢' : '🌳';
|
|
const typeLabel = result.indoor ? 'Indoor' : 'Buiten';
|
|
|
|
console.log(`\n${typeIcon} Peakz Padel — ${result.location} (${typeLabel})`);
|
|
console.log(`📅 ${dayLabel}`);
|
|
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.slice(0, 15)) {
|
|
console.log(` 🟢 ${s.time}${s.price ? ' — €' + s.price : ''}`);
|
|
}
|
|
if (result.available.length > 15) console.log(` ... en ${result.available.length - 15} meer`);
|
|
}
|
|
console.log('');
|
|
process.exit(0);
|
|
});
|