2022-03-29 22:41:44 +02:00
|
|
|
import dayjs, { Dayjs } from 'dayjs'
|
2021-11-17 14:17:25 +01:00
|
|
|
import puppeteer, {
|
|
|
|
|
Browser,
|
|
|
|
|
BrowserConnectOptions,
|
|
|
|
|
BrowserLaunchArgumentOptions,
|
|
|
|
|
ElementHandle,
|
|
|
|
|
LaunchOptions,
|
|
|
|
|
Page,
|
|
|
|
|
} from 'puppeteer'
|
2022-03-29 22:41:44 +02:00
|
|
|
import { Logger } from './logger'
|
2021-11-24 00:00:22 +01:00
|
|
|
import { Opponent, Reservation } from './reservation'
|
2021-11-15 11:28:39 +01:00
|
|
|
|
|
|
|
|
export class Runner {
|
|
|
|
|
private browser: Browser | undefined
|
|
|
|
|
private page: Page | undefined
|
2022-11-29 22:51:28 +01:00
|
|
|
private options?: LaunchOptions &
|
|
|
|
|
BrowserLaunchArgumentOptions &
|
|
|
|
|
BrowserConnectOptions
|
2021-11-15 11:28:39 +01:00
|
|
|
|
2022-11-29 22:51:28 +01:00
|
|
|
constructor(
|
2021-11-17 14:17:25 +01:00
|
|
|
options?: LaunchOptions &
|
|
|
|
|
BrowserLaunchArgumentOptions &
|
|
|
|
|
BrowserConnectOptions
|
2022-11-29 22:51:28 +01:00
|
|
|
) {
|
|
|
|
|
this.options = options
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public async run(reservation: Reservation): Promise<boolean> {
|
2022-10-23 11:55:47 +02:00
|
|
|
Logger.debug('Launching browser')
|
2022-11-29 22:51:28 +01:00
|
|
|
if (!this.browser) {
|
|
|
|
|
this.browser = await puppeteer.launch(this.options)
|
|
|
|
|
}
|
2021-11-28 19:40:16 +01:00
|
|
|
this.page = await this.browser?.newPage()
|
2022-11-29 22:51:28 +01:00
|
|
|
await this.login(reservation.user.username, reservation.user.password)
|
|
|
|
|
return this.makeReservation(reservation)
|
2021-11-15 11:28:39 +01:00
|
|
|
}
|
|
|
|
|
|
2022-11-29 22:51:28 +01:00
|
|
|
private async login(username: string, password: string) {
|
2022-10-23 11:55:47 +02:00
|
|
|
Logger.debug('Logging in')
|
2021-11-17 14:17:25 +01:00
|
|
|
await this.page?.goto('https://squashcity.baanreserveren.nl/')
|
|
|
|
|
await this.page
|
|
|
|
|
?.waitForSelector('input[name=username]')
|
2022-11-29 22:51:28 +01:00
|
|
|
.then((i) => i?.type(username))
|
|
|
|
|
await this.page?.$('input[name=password]').then((i) => i?.type(password))
|
2021-11-17 14:17:25 +01:00
|
|
|
await this.page?.$('button').then((b) => b?.click())
|
2021-11-15 11:28:39 +01:00
|
|
|
}
|
|
|
|
|
|
2022-11-29 22:51:28 +01:00
|
|
|
private async makeReservation(reservation: Reservation): Promise<boolean> {
|
2021-11-15 11:28:39 +01:00
|
|
|
try {
|
2021-11-24 00:00:22 +01:00
|
|
|
await this.navigateToDay(reservation.dateRange.start)
|
2021-11-15 11:28:39 +01:00
|
|
|
await this.selectAvailableTime(reservation)
|
|
|
|
|
await this.selectOpponent(reservation.opponent)
|
|
|
|
|
await this.confirmReservation()
|
|
|
|
|
reservation.booked = true
|
2022-11-29 22:51:28 +01:00
|
|
|
return true
|
2021-11-15 11:28:39 +01:00
|
|
|
} catch (err) {
|
2022-10-23 11:55:47 +02:00
|
|
|
Logger.error('Error making reservation', reservation.format())
|
2022-11-29 22:51:28 +01:00
|
|
|
return false
|
2022-03-29 22:41:44 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private getLastVisibleDay(): Dayjs {
|
2022-10-23 11:55:47 +02:00
|
|
|
const lastDayOfMonth = dayjs().add(1, 'month').set('date', 0)
|
|
|
|
|
let daysToAdd = 0
|
2022-03-29 22:41:44 +02:00
|
|
|
switch (lastDayOfMonth.day()) {
|
2022-10-23 11:55:47 +02:00
|
|
|
case 0:
|
|
|
|
|
daysToAdd = 0
|
|
|
|
|
break
|
|
|
|
|
default:
|
|
|
|
|
daysToAdd = 7 - lastDayOfMonth.day()
|
|
|
|
|
break
|
2021-11-15 11:28:39 +01:00
|
|
|
}
|
2022-10-23 11:55:47 +02:00
|
|
|
return lastDayOfMonth.add(daysToAdd, 'day')
|
2021-11-15 11:28:39 +01:00
|
|
|
}
|
|
|
|
|
|
2021-11-24 00:00:22 +01:00
|
|
|
private async navigateToDay(date: Dayjs): Promise<void> {
|
2022-10-23 11:55:47 +02:00
|
|
|
Logger.debug(`Navigating to ${date.format()}`)
|
2022-03-29 22:41:44 +02:00
|
|
|
|
|
|
|
|
if (this.getLastVisibleDay().isBefore(date)) {
|
2022-10-23 11:55:47 +02:00
|
|
|
Logger.debug('Date is on different page, increase month')
|
|
|
|
|
await this.page?.waitForSelector('td.month.next').then((d) => d?.click())
|
2022-03-29 22:41:44 +02:00
|
|
|
}
|
|
|
|
|
|
2021-11-17 14:17:25 +01:00
|
|
|
await this.page
|
2021-11-24 00:00:22 +01:00
|
|
|
?.waitForSelector(
|
2022-10-23 11:55:47 +02:00
|
|
|
`td#cal_${date.get('year')}_${date.get('month') + 1}_${date.get(
|
|
|
|
|
'date'
|
|
|
|
|
)}`
|
2021-11-24 00:00:22 +01:00
|
|
|
)
|
2021-11-17 14:17:25 +01:00
|
|
|
.then((d) => d?.click())
|
|
|
|
|
await this.page?.waitForSelector(
|
2021-11-24 00:00:22 +01:00
|
|
|
`td#cal_${date.get('year')}_${date.get('month') + 1}_${date.get(
|
2022-03-29 22:41:44 +02:00
|
|
|
'date'
|
2021-11-24 00:00:22 +01:00
|
|
|
)}.selected`
|
2021-11-17 14:17:25 +01:00
|
|
|
)
|
2021-11-15 11:28:39 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private async selectAvailableTime(res: Reservation): Promise<void> {
|
2022-10-23 11:55:47 +02:00
|
|
|
Logger.debug('Selecting available time', res.format())
|
2021-11-17 14:17:25 +01:00
|
|
|
let freeCourt: ElementHandle | null | undefined
|
2021-11-15 11:28:39 +01:00
|
|
|
let i = 0
|
2021-11-24 00:00:22 +01:00
|
|
|
while (i < res.possibleDates.length && !freeCourt) {
|
|
|
|
|
const possibleDate = res.possibleDates[i]
|
|
|
|
|
const timeString = possibleDate.format('HH:mm')
|
2021-11-17 14:17:25 +01:00
|
|
|
const selector =
|
|
|
|
|
`tr[data-time='${timeString}']` + `> td.free[rowspan='3'][type='free']`
|
|
|
|
|
freeCourt = await this.page?.$(selector)
|
|
|
|
|
i++
|
2021-11-15 11:28:39 +01:00
|
|
|
}
|
|
|
|
|
|
2021-11-17 14:17:25 +01:00
|
|
|
if (!freeCourt) {
|
|
|
|
|
throw new Error('No free court available')
|
2021-11-15 11:28:39 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
await freeCourt.click()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private async selectOpponent(opponent: Opponent): Promise<void> {
|
2022-10-23 11:55:47 +02:00
|
|
|
Logger.debug('Selecting opponent', opponent)
|
2021-11-17 14:17:25 +01:00
|
|
|
const player2Search = await this.page?.waitForSelector(
|
|
|
|
|
'tr.res-make-player-2 > td > input'
|
|
|
|
|
)
|
|
|
|
|
await player2Search?.type(opponent.name)
|
|
|
|
|
await this.page?.waitForNetworkIdle()
|
|
|
|
|
await this.page
|
|
|
|
|
?.$('select.br-user-select[name="players[2]"]')
|
|
|
|
|
.then((d) => d?.select(opponent.id))
|
2021-11-15 11:28:39 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private async confirmReservation(): Promise<void> {
|
2021-11-17 14:17:25 +01:00
|
|
|
await this.page?.$('input#__make_submit').then((b) => b?.click())
|
|
|
|
|
await this.page
|
|
|
|
|
?.waitForSelector('input#__make_submit2')
|
|
|
|
|
.then((b) => b?.click())
|
2021-11-15 11:28:39 +01:00
|
|
|
}
|
2021-11-17 14:17:25 +01:00
|
|
|
}
|