Adding a warmup and changing daily reservations to utilize this warmup + busy loop to be faster in the morning
This commit is contained in:
parent
73b32402d3
commit
e7bff228c6
6 changed files with 86 additions and 52 deletions
|
|
@ -24,6 +24,8 @@ export interface SerializedDateRange {
|
||||||
end: string
|
end: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const setDefaults = () => dayjs.tz.setDefault('Europe/Amsterdam')
|
||||||
|
|
||||||
export const convertDateRangeStringToObject = ({
|
export const convertDateRangeStringToObject = ({
|
||||||
start,
|
start,
|
||||||
end,
|
end,
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import { NestFactory } from '@nestjs/core'
|
||||||
|
|
||||||
import { AppModule } from './app.module'
|
import { AppModule } from './app.module'
|
||||||
import { CustomResponseTransformInterceptor } from './common/customResponse'
|
import { CustomResponseTransformInterceptor } from './common/customResponse'
|
||||||
|
import { setDefaults } from './common/dayjs'
|
||||||
|
|
||||||
async function bootstrap() {
|
async function bootstrap() {
|
||||||
const app = await NestFactory.create(AppModule, { abortOnError: false })
|
const app = await NestFactory.create(AppModule, { abortOnError: false })
|
||||||
|
|
@ -12,6 +13,7 @@ async function bootstrap() {
|
||||||
app.enableShutdownHooks()
|
app.enableShutdownHooks()
|
||||||
app.useGlobalPipes(new ValidationPipe({ transform: true }))
|
app.useGlobalPipes(new ValidationPipe({ transform: true }))
|
||||||
app.useGlobalInterceptors(new CustomResponseTransformInterceptor())
|
app.useGlobalInterceptors(new CustomResponseTransformInterceptor())
|
||||||
|
setDefaults()
|
||||||
await app.listen(port, () => console.log(`Listening on port ${port}`))
|
await app.listen(port, () => console.log(`Listening on port ${port}`))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,10 @@
|
||||||
import { repl } from '@nestjs/core'
|
import { repl } from '@nestjs/core'
|
||||||
|
|
||||||
import { AppModule } from './app.module'
|
import { AppModule } from './app.module'
|
||||||
|
import { setDefaults } from './common/dayjs'
|
||||||
|
|
||||||
async function bootstrap() {
|
async function bootstrap() {
|
||||||
|
setDefaults()
|
||||||
await repl(AppModule)
|
await repl(AppModule)
|
||||||
}
|
}
|
||||||
bootstrap()
|
bootstrap()
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,7 @@ export class ReservationsCronService {
|
||||||
private readonly loggerService: LoggerService,
|
private readonly loggerService: LoggerService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
@Cron(CronExpression.EVERY_DAY_AT_7AM, {
|
@Cron('55 06 * * *', {
|
||||||
name: 'handleDailyReservations',
|
name: 'handleDailyReservations',
|
||||||
timeZone: 'Europe/Amsterdam',
|
timeZone: 'Europe/Amsterdam',
|
||||||
})
|
})
|
||||||
|
|
@ -42,12 +42,36 @@ export class ReservationsCronService {
|
||||||
this.loggerService.log(
|
this.loggerService.log(
|
||||||
`Found ${reservationsToPerform.length} reservations to perform`,
|
`Found ${reservationsToPerform.length} reservations to perform`,
|
||||||
)
|
)
|
||||||
await this.reservationsQueue.addBulk(
|
|
||||||
reservationsToPerform.map((r) => ({
|
// In order to make sure session is fresh and speed up some shit let's warm him up
|
||||||
data: r,
|
await this.brService.warmup()
|
||||||
opts: { attempts: DAILY_RESERVATIONS_ATTEMPTS },
|
|
||||||
})),
|
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 {
|
} else {
|
||||||
this.loggerService.log('Monitoring reservations')
|
this.loggerService.log('Monitoring reservations')
|
||||||
await this.brService.monitorCourtReservations(dayjs().add(7, 'day'))
|
await this.brService.monitorCourtReservations(dayjs().add(7, 'day'))
|
||||||
|
|
|
||||||
|
|
@ -51,7 +51,7 @@ export class ReservationsService {
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.andWhere(`waitListed = false`)
|
.andWhere(`waitListed = false`)
|
||||||
.orderBy('dateRangeStart', 'DESC')
|
.orderBy('dateRangeStart', 'ASC')
|
||||||
|
|
||||||
return await query.getMany()
|
return await query.getMany()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -82,7 +82,7 @@ const CourtRank = {
|
||||||
[CourtSlot.Thirteen]: 1, // no one likes upstairs
|
[CourtSlot.Thirteen]: 1, // no one likes upstairs
|
||||||
} as const
|
} as const
|
||||||
|
|
||||||
const TYPING_DELAY_MS = 5
|
const TYPING_DELAY_MS = 2
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class BaanReserverenService {
|
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) {
|
private async checkSession(username: string) {
|
||||||
this.loggerService.debug('Checking session', {
|
this.loggerService.debug('Checking session', {
|
||||||
username,
|
username,
|
||||||
session: this.session,
|
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
|
if (!this.page.url().includes(BAAN_RESERVEREN_ROOT_URL)) {
|
||||||
? SessionAction.Logout
|
await this.navigateToReservations()
|
||||||
: SessionAction.NoAction
|
|
||||||
}
|
}
|
||||||
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) {
|
private startSession(username: string) {
|
||||||
|
|
@ -202,8 +203,7 @@ export class BaanReserverenService {
|
||||||
|
|
||||||
private async init() {
|
private async init() {
|
||||||
this.loggerService.debug('Initializing')
|
this.loggerService.debug('Initializing')
|
||||||
await this.page.goto(BAAN_RESERVEREN_ROOT_URL)
|
await this.navigateToReservations()
|
||||||
await this.page.waitForNetworkIdle()
|
|
||||||
const action = await this.checkSession(this.username)
|
const action = await this.checkSession(this.username)
|
||||||
switch (action) {
|
switch (action) {
|
||||||
case SessionAction.Logout:
|
case SessionAction.Logout:
|
||||||
|
|
@ -533,6 +533,37 @@ export class BaanReserverenService {
|
||||||
await this.page.waitForNetworkIdle()
|
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) {
|
public async performReservation(reservation: Reservation) {
|
||||||
try {
|
try {
|
||||||
await this.init()
|
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) {
|
public async monitorCourtReservations(date?: Dayjs, swallowError = true) {
|
||||||
try {
|
try {
|
||||||
if (date) {
|
if (date) {
|
||||||
|
|
@ -642,6 +642,10 @@ export class BaanReserverenService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async warmup() {
|
||||||
|
await this.init()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class RunnerError extends Error {
|
export class RunnerError extends Error {
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue