Small bug fix in reservation loop and prettier/eslint

This commit is contained in:
Collin Duncan 2021-11-17 14:17:25 +01:00
parent 8a83417b7d
commit 5055d5cd91
No known key found for this signature in database
7 changed files with 2445 additions and 85 deletions

2314
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -7,11 +7,13 @@
"node": "14.x"
},
"scripts": {
"lint": "eslint src/ --ext ts",
"prettier": "prettier src -w",
"build": "tsc",
"local": "npx ts-node src/local.ts",
"zip": "mkdir deploy && zip deploy/reservation-lambda.zip -r dist"
"zip": "mkdir deploy && zip deploy/reservation-lambda.zip -r dist",
},
"author": "",
"author": "Collin Duncan <cgduncan7@gmail.com>",
"license": "ISC",
"dependencies": {
"puppeteer": "^11.0.0"
@ -23,6 +25,10 @@
"@rollup/plugin-babel": "^5.3.0",
"@types/aws-lambda": "^8.10.85",
"@types/puppeteer": "^5.4.4",
"@typescript-eslint/eslint-plugin": "^5.4.0",
"@typescript-eslint/parser": "^5.4.0",
"eslint": "^8.2.0",
"prettier": "^2.4.1",
"typescript": "^4.4.4"
}
}

View file

@ -1,5 +1,5 @@
import { SQSEvent, SQSHandler } from "aws-lambda"
import { validateRequestEvent } from "./request"
import { SQSEvent, SQSHandler } from 'aws-lambda'
import { validateRequestEvent } from './request'
import { Reservation } from './reservation'
import { Runner } from './runner'
@ -15,4 +15,4 @@ export const run: SQSHandler = async (event: SQSEvent): Promise<void> => {
const runner = new Runner(username, password, reservations)
await runner.run({ headless: false })
}
}

View file

@ -1,7 +1,6 @@
import { IncomingRequest, validateRequestEvent } from "./request"
import { Reservation } from "./reservation"
import { Runner } from "./runner"
import { IncomingRequest } from './request'
import { Reservation } from './reservation'
import { Runner } from './runner'
const run = async (request: IncomingRequest) => {
const { username, password, dateTimes, opponent } = request
@ -14,36 +13,52 @@ const run = async (request: IncomingRequest) => {
// get supplied args
const args = process.argv.filter((_, index) => index >= 2)
if (args.length !== 7) {
console.error('Usage: npm run local <username> <password> <year> <month> <day> <startTime> <endTime> <opponentName> <opponentId>')
console.error(
'Usage: npm run local <username> <password> <year> <month> <day> <startTime> <endTime> <opponentName> <opponentId>'
)
process.exit(1)
}
const [username, password, year, month, day, startTime, endTime, opponentName, opponentId] = args
const [startHour, startMinute] = startTime.split(':').map((t) => Number.parseInt(t))
const [
username,
password,
year,
month,
day,
startTime,
endTime,
opponentName,
opponentId,
] = args
const [startHour, startMinute] = startTime
.split(':')
.map((t) => Number.parseInt(t))
const [endHour, endMinute] = endTime.split(':').map((t) => Number.parseInt(t))
run({
username: username,
password: password,
dateTimes: [{
year: Number.parseInt(year),
month: Number.parseInt(month),
day: Number.parseInt(day),
timeRange: {
start: {
hour: startHour,
minute: startMinute,
},
end: {
hour: endHour,
minute: endMinute,
dateTimes: [
{
year: Number.parseInt(year),
month: Number.parseInt(month),
day: Number.parseInt(day),
timeRange: {
start: {
hour: startHour,
minute: startMinute,
},
end: {
hour: endHour,
minute: endMinute,
},
},
},
}],
],
opponent: {
id: opponentId,
name: opponentName,
},
})
.then(() => console.log('Success'))
.catch((e) => console.error(e))
.catch((e) => console.error(e))

View file

@ -1,9 +1,5 @@
import {
APIGatewayProxyEventV2, SQSEvent,
} from "aws-lambda";
import {
DateTime, Opponent
} from "./reservation";
import { SQSEvent } from 'aws-lambda'
import { DateTime, Opponent } from './reservation'
export interface IncomingRequest {
username: string
@ -17,14 +13,16 @@ export interface ValidationError {
code: number
}
export const validateRequestEvent = (event: SQSEvent): { request?: IncomingRequest, error ?: ValidationError } => {
export const validateRequestEvent = (
event: SQSEvent
): { request?: IncomingRequest; error?: ValidationError } => {
try {
const request = validateRequestBody(event.Records[0].body)
validateRequestDateTimes(request.dateTimes)
validateRequestOpponent(request.opponent)
return { request }
} catch (err: any) {
return { error: { message: 'Invalid request', code: err.code ?? 0 } }
} catch (err: unknown) {
return { error: { message: 'Invalid request', code: (err as ValidationError).code ?? 0 } }
}
}
@ -32,7 +30,7 @@ const validateRequestBody = (body?: string): IncomingRequest => {
if (body === undefined) {
throw {
message: 'Invalid request',
code: 1
code: 1,
}
}
@ -46,12 +44,14 @@ const validateRequestBody = (body?: string): IncomingRequest => {
}
}
const {
username,
password,
dateTimes,
} = jsonBody
if (!username || username.length < 1 || !password || password.length < 1 || !dateTimes) {
const { username, password, dateTimes } = jsonBody
if (
!username ||
username.length < 1 ||
!password ||
password.length < 1 ||
!dateTimes
) {
throw {
message: 'Invalid request',
code: 3,
@ -65,23 +65,17 @@ const validateRequestDateTimes = (dateTimes: DateTime[]): void => {
const now = new Date()
for (let i = 0; i < dateTimes.length; i++) {
const dateTime = dateTimes[i]
const {
year,
month,
day,
timeRange
} = dateTime
const {
start,
end
} = timeRange
const { year, month, day, timeRange } = dateTime
const { start, end } = timeRange
if (
typeof year !== 'number' ||
typeof month !== 'number' ||
typeof day !== 'number' ||
typeof start.hour !== 'number' || typeof start.minute !== 'number' ||
typeof end.hour !== 'number' || typeof end.minute !== 'number'
typeof start.hour !== 'number' ||
typeof start.minute !== 'number' ||
typeof end.hour !== 'number' ||
typeof end.minute !== 'number'
) {
throw {
message: 'Invalid request',
@ -117,4 +111,4 @@ const validateRequestOpponent = (opponent?: Opponent): void => {
code: 6,
}
}
}
}

View file

@ -18,15 +18,16 @@ export interface DateTime {
}
}
export const timeToString = ({ hour, minute }: Time): string => `${`${hour}`.padStart(2, '0')}:${`${minute}`.padStart(2, '0')}`
export const timeToString = ({ hour, minute }: Time): string =>
`${`${hour}`.padStart(2, '0')}:${`${minute}`.padStart(2, '0')}`
export class Reservation {
public readonly dateTime: DateTime
public readonly opponent: Opponent
public readonly possibleTimes: Time[]
public booked: boolean = false
public booked = false
constructor (dateTime: DateTime, opponent: Opponent) {
constructor(dateTime: DateTime, opponent: Opponent) {
this.dateTime = dateTime
this.opponent = opponent
this.possibleTimes = this.createPossibleTimes()
@ -36,9 +37,9 @@ export class Reservation {
const possibleTimes: Time[] = []
const { start, end } = this.dateTime.timeRange
let { hour, minute } = start
let { hour: endHour, minute: endMinute } = end
const { hour: endHour, minute: endMinute } = end
while (hour <= endHour && minute <= endMinute) {
possibleTimes.push({ hour, minute })
@ -50,4 +51,4 @@ export class Reservation {
return possibleTimes
}
}
}

View file

@ -1,5 +1,12 @@
import puppeteer, { Browser, BrowserConnectOptions, BrowserLaunchArgumentOptions, ElementHandle, LaunchOptions, Page } from "puppeteer"
import { DateTime, Opponent, Reservation, timeToString } from "./reservation";
import puppeteer, {
Browser,
BrowserConnectOptions,
BrowserLaunchArgumentOptions,
ElementHandle,
LaunchOptions,
Page,
} from 'puppeteer'
import { DateTime, Opponent, Reservation, timeToString } from './reservation'
export class Runner {
private readonly username: string
@ -9,13 +16,21 @@ export class Runner {
private browser: Browser | undefined
private page: Page | undefined
public constructor(username: string, password: string, reservations: Reservation[], options?: LaunchOptions) {
public constructor(
username: string,
password: string,
reservations: Reservation[]
) {
this.username = username
this.password = password
this.reservations = reservations
}
public async run(options?: LaunchOptions & BrowserLaunchArgumentOptions & BrowserConnectOptions): Promise<Reservation[]> {
public async run(
options?: LaunchOptions &
BrowserLaunchArgumentOptions &
BrowserConnectOptions
): Promise<Reservation[]> {
this.browser = await puppeteer.launch(options)
this.page = await this.browser.newPage()
await this.login()
@ -23,10 +38,14 @@ export class Runner {
}
private async login() {
await this.page!.goto('https://squashcity.baanreserveren.nl/')
await this.page!.waitForSelector('input[name=username]').then(i => i!.type(this.username))
await this.page!.$('input[name=password]').then(i => i!.type(this.password))
await this.page!.$('button').then((b) => b!.click())
await this.page?.goto('https://squashcity.baanreserveren.nl/')
await this.page
?.waitForSelector('input[name=username]')
.then((i) => i?.type(this.username))
await this.page
?.$('input[name=password]')
.then((i) => i?.type(this.password))
await this.page?.$('button').then((b) => b?.click())
}
private async makeReservations(): Promise<Reservation[]> {
@ -50,37 +69,48 @@ export class Runner {
}
private async navigateToDay(dt: DateTime): Promise<void> {
await this.page!.waitForSelector(`td#cal_${dt.year}_${dt.month}_${dt.day}`).then((d) => d!.click())
await this.page!.waitForSelector(`td#cal_${dt.year}_${dt.month}_${dt.day}.selected`)
await this.page
?.waitForSelector(`td#cal_${dt.year}_${dt.month}_${dt.day}`)
.then((d) => d?.click())
await this.page?.waitForSelector(
`td#cal_${dt.year}_${dt.month}_${dt.day}.selected`
)
}
private async selectAvailableTime(res: Reservation): Promise<void> {
let freeCourt: ElementHandle | null = null
let freeCourt: ElementHandle | null | undefined
let i = 0
while (i < res.possibleTimes.length && freeCourt !== null) {
while (i < res.possibleTimes.length && !freeCourt) {
const possibleTime = res.possibleTimes[i]
const timeString = timeToString(possibleTime)
const selector = `tr[data-time='${timeString}']` +
`> td.free[rowspan='3'][type='free']`
freeCourt = await this.page!.$(selector)
const selector =
`tr[data-time='${timeString}']` + `> td.free[rowspan='3'][type='free']`
freeCourt = await this.page?.$(selector)
i++
}
if (freeCourt === null) {
throw new Error("No free court available")
if (!freeCourt) {
throw new Error('No free court available')
}
await freeCourt.click()
}
private async selectOpponent(opponent: Opponent): Promise<void> {
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))
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))
}
private async confirmReservation(): Promise<void> {
await this.page!.$('input#__make_submit').then(b => b!.click())
await this.page!.waitForSelector("input#__make_submit2").then(b => b!.click())
await this.page?.$('input#__make_submit').then((b) => b?.click())
await this.page
?.waitForSelector('input#__make_submit2')
.then((b) => b?.click())
}
}
}