Adding a warmup and changing daily reservations to utilize this warmup + busy loop to be faster in the morning

This commit is contained in:
Collin Duncan 2024-04-09 23:19:57 +02:00
parent 73b32402d3
commit e7bff228c6
No known key found for this signature in database
6 changed files with 86 additions and 52 deletions

View file

@ -24,6 +24,8 @@ export interface SerializedDateRange {
end: string
}
export const setDefaults = () => dayjs.tz.setDefault('Europe/Amsterdam')
export const convertDateRangeStringToObject = ({
start,
end,

View file

@ -4,6 +4,7 @@ import { NestFactory } from '@nestjs/core'
import { AppModule } from './app.module'
import { CustomResponseTransformInterceptor } from './common/customResponse'
import { setDefaults } from './common/dayjs'
async function bootstrap() {
const app = await NestFactory.create(AppModule, { abortOnError: false })
@ -12,6 +13,7 @@ async function bootstrap() {
app.enableShutdownHooks()
app.useGlobalPipes(new ValidationPipe({ transform: true }))
app.useGlobalInterceptors(new CustomResponseTransformInterceptor())
setDefaults()
await app.listen(port, () => console.log(`Listening on port ${port}`))
}

View file

@ -1,8 +1,10 @@
import { repl } from '@nestjs/core'
import { AppModule } from './app.module'
import { setDefaults } from './common/dayjs'
async function bootstrap() {
setDefaults()
await repl(AppModule)
}
bootstrap()

View file

@ -30,7 +30,7 @@ export class ReservationsCronService {
private readonly loggerService: LoggerService,
) {}
@Cron(CronExpression.EVERY_DAY_AT_7AM, {
@Cron('55 06 * * *', {
name: 'handleDailyReservations',
timeZone: 'Europe/Amsterdam',
})
@ -42,12 +42,36 @@ export class ReservationsCronService {
this.loggerService.log(
`Found ${reservationsToPerform.length} reservations to perform`,
)
await this.reservationsQueue.addBulk(
reservationsToPerform.map((r) => ({
data: r,
opts: { attempts: DAILY_RESERVATIONS_ATTEMPTS },
})),
)
// In order to make sure session is fresh and speed up some shit let's warm him up
await this.brService.warmup()
this.loggerService.log(`Warmed up! Waiting for go-time`)
let not7AM = true
const waitTime = 10
const time7AM = dayjs()
.set('hour', 7)
.set('minute', 0)
.set('second', 0)
.set('millisecond', 0)
while (not7AM) {
not7AM = time7AM.isBefore(dayjs()) && time7AM.diff(dayjs()) >= waitTime // current time is more than 100ms from 7am
if (!not7AM) break
await new Promise((res) => setTimeout(res, waitTime)) // wait for waitTime and then try again
}
this.loggerService.log(`It's go-time`)
for (const res of reservationsToPerform) {
await this.brService.performReservation(res).catch(
async () =>
await this.reservationsQueue.add(res, {
attempts: Math.max(DAILY_RESERVATIONS_ATTEMPTS - 1, 1),
}),
)
}
} else {
this.loggerService.log('Monitoring reservations')
await this.brService.monitorCourtReservations(dayjs().add(7, 'day'))

View file

@ -51,7 +51,7 @@ export class ReservationsService {
},
)
.andWhere(`waitListed = false`)
.orderBy('dateRangeStart', 'DESC')
.orderBy('dateRangeStart', 'ASC')
return await query.getMany()
}

View file

@ -82,7 +82,7 @@ const CourtRank = {
[CourtSlot.Thirteen]: 1, // no one likes upstairs
} as const
const TYPING_DELAY_MS = 5
const TYPING_DELAY_MS = 2
@Injectable()
export class BaanReserverenService {
@ -127,23 +127,24 @@ export class BaanReserverenService {
)
}
// Check session by going to /reservations to see if we are still logged in via cookies
private async checkSession(username: string) {
this.loggerService.debug('Checking session', {
username,
session: this.session,
})
if (this.page.url().includes(BAAN_RESERVEREN_ROOT_URL)) {
// Check session by going to /reservations to see if we are still logged in via cookies
await this.navigateToReservations()
if (this.page.url().includes('?reason=LOGGED_IN')) {
return SessionAction.Login
}
return this.session?.username !== this.username
? SessionAction.Logout
: SessionAction.NoAction
if (!this.page.url().includes(BAAN_RESERVEREN_ROOT_URL)) {
await this.navigateToReservations()
}
return SessionAction.Login
if (this.page.url().includes('?reason=LOGGED_IN')) {
return SessionAction.Login
}
return this.session?.username !== this.username
? SessionAction.Logout
: SessionAction.NoAction
}
private startSession(username: string) {
@ -202,8 +203,7 @@ export class BaanReserverenService {
private async init() {
this.loggerService.debug('Initializing')
await this.page.goto(BAAN_RESERVEREN_ROOT_URL)
await this.page.waitForNetworkIdle()
await this.navigateToReservations()
const action = await this.checkSession(this.username)
switch (action) {
case SessionAction.Logout:
@ -533,6 +533,37 @@ export class BaanReserverenService {
await this.page.waitForNetworkIdle()
}
private async getAllCourtStatuses() {
const courts = await this.page.$$('tr > td.slot')
const courtStatuses: {
courtNumber: string
startTime: string
status: string
duration: string
}[] = []
for (const court of courts) {
const classListObj = await (
await court.getProperty('classList')
).jsonValue()
const classList = Object.values(classListObj)
const rClass = classList.filter((cl) => /r-\d{2}/.test(cl))[0]
const courtNumber =
`${CourtSlotToNumber[rClass.replace(/r-/, '') as CourtSlot]}` ??
'unknown court'
const startTime = await court
.$eval('div.slot-period', (e) => e.innerText.trim())
.catch(() => 'unknown')
const status = classList.includes('free') ? 'available' : 'unavailable'
const courtRowSpan = await (
await court.getProperty('rowSpan')
).jsonValue()
const duration = `${Number(courtRowSpan ?? '0') * 15} minutes`
courtStatuses.push({ courtNumber, startTime, status, duration }) //const d = require('dayjs'); await get(BaanReserverenService).monitorCourtReservations(d());
}
return courtStatuses
}
public async performReservation(reservation: Reservation) {
try {
await this.init()
@ -588,37 +619,6 @@ export class BaanReserverenService {
}
}
private async getAllCourtStatuses() {
const courts = await this.page.$$('tr > td.slot')
const courtStatuses: {
courtNumber: string
startTime: string
status: string
duration: string
}[] = []
for (const court of courts) {
const classListObj = await (
await court.getProperty('classList')
).jsonValue()
const classList = Object.values(classListObj)
const rClass = classList.filter((cl) => /r-\d{2}/.test(cl))[0]
const courtNumber =
`${CourtSlotToNumber[rClass.replace(/r-/, '') as CourtSlot]}` ??
'unknown court'
const startTime = await court
.$eval('div.slot-period', (e) => e.innerText.trim())
.catch(() => 'unknown')
const status = classList.includes('free') ? 'available' : 'unavailable'
const courtRowSpan = await (
await court.getProperty('rowSpan')
).jsonValue()
const duration = `${Number(courtRowSpan ?? '0') * 15} minutes`
courtStatuses.push({ courtNumber, startTime, status, duration }) //const d = require('dayjs'); await get(BaanReserverenService).monitorCourtReservations(d());
}
return courtStatuses
}
public async monitorCourtReservations(date?: Dayjs, swallowError = true) {
try {
if (date) {
@ -642,6 +642,10 @@ export class BaanReserverenService {
}
}
}
public async warmup() {
await this.init()
}
}
export class RunnerError extends Error {