Small bug fix in reservation loop and prettier/eslint
This commit is contained in:
parent
8a83417b7d
commit
5055d5cd91
7 changed files with 2445 additions and 85 deletions
2314
package-lock.json
generated
2314
package-lock.json
generated
File diff suppressed because it is too large
Load diff
10
package.json
10
package.json
|
|
@ -7,11 +7,13 @@
|
||||||
"node": "14.x"
|
"node": "14.x"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
"lint": "eslint src/ --ext ts",
|
||||||
|
"prettier": "prettier src -w",
|
||||||
"build": "tsc",
|
"build": "tsc",
|
||||||
"local": "npx ts-node src/local.ts",
|
"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",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"puppeteer": "^11.0.0"
|
"puppeteer": "^11.0.0"
|
||||||
|
|
@ -23,6 +25,10 @@
|
||||||
"@rollup/plugin-babel": "^5.3.0",
|
"@rollup/plugin-babel": "^5.3.0",
|
||||||
"@types/aws-lambda": "^8.10.85",
|
"@types/aws-lambda": "^8.10.85",
|
||||||
"@types/puppeteer": "^5.4.4",
|
"@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"
|
"typescript": "^4.4.4"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { SQSEvent, SQSHandler } from "aws-lambda"
|
import { SQSEvent, SQSHandler } from 'aws-lambda'
|
||||||
import { validateRequestEvent } from "./request"
|
import { validateRequestEvent } from './request'
|
||||||
|
|
||||||
import { Reservation } from './reservation'
|
import { Reservation } from './reservation'
|
||||||
import { Runner } from './runner'
|
import { Runner } from './runner'
|
||||||
|
|
|
||||||
55
src/local.ts
55
src/local.ts
|
|
@ -1,7 +1,6 @@
|
||||||
import { IncomingRequest, validateRequestEvent } from "./request"
|
import { IncomingRequest } from './request'
|
||||||
import { Reservation } from "./reservation"
|
import { Reservation } from './reservation'
|
||||||
import { Runner } from "./runner"
|
import { Runner } from './runner'
|
||||||
|
|
||||||
|
|
||||||
const run = async (request: IncomingRequest) => {
|
const run = async (request: IncomingRequest) => {
|
||||||
const { username, password, dateTimes, opponent } = request
|
const { username, password, dateTimes, opponent } = request
|
||||||
|
|
@ -14,32 +13,48 @@ const run = async (request: IncomingRequest) => {
|
||||||
// get supplied args
|
// get supplied args
|
||||||
const args = process.argv.filter((_, index) => index >= 2)
|
const args = process.argv.filter((_, index) => index >= 2)
|
||||||
if (args.length !== 7) {
|
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)
|
process.exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
const [username, password, year, month, day, startTime, endTime, opponentName, opponentId] = args
|
const [
|
||||||
const [startHour, startMinute] = startTime.split(':').map((t) => Number.parseInt(t))
|
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))
|
const [endHour, endMinute] = endTime.split(':').map((t) => Number.parseInt(t))
|
||||||
|
|
||||||
run({
|
run({
|
||||||
username: username,
|
username: username,
|
||||||
password: password,
|
password: password,
|
||||||
dateTimes: [{
|
dateTimes: [
|
||||||
year: Number.parseInt(year),
|
{
|
||||||
month: Number.parseInt(month),
|
year: Number.parseInt(year),
|
||||||
day: Number.parseInt(day),
|
month: Number.parseInt(month),
|
||||||
timeRange: {
|
day: Number.parseInt(day),
|
||||||
start: {
|
timeRange: {
|
||||||
hour: startHour,
|
start: {
|
||||||
minute: startMinute,
|
hour: startHour,
|
||||||
},
|
minute: startMinute,
|
||||||
end: {
|
},
|
||||||
hour: endHour,
|
end: {
|
||||||
minute: endMinute,
|
hour: endHour,
|
||||||
|
minute: endMinute,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}],
|
],
|
||||||
opponent: {
|
opponent: {
|
||||||
id: opponentId,
|
id: opponentId,
|
||||||
name: opponentName,
|
name: opponentName,
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,5 @@
|
||||||
import {
|
import { SQSEvent } from 'aws-lambda'
|
||||||
APIGatewayProxyEventV2, SQSEvent,
|
import { DateTime, Opponent } from './reservation'
|
||||||
} from "aws-lambda";
|
|
||||||
import {
|
|
||||||
DateTime, Opponent
|
|
||||||
} from "./reservation";
|
|
||||||
|
|
||||||
export interface IncomingRequest {
|
export interface IncomingRequest {
|
||||||
username: string
|
username: string
|
||||||
|
|
@ -17,14 +13,16 @@ export interface ValidationError {
|
||||||
code: number
|
code: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export const validateRequestEvent = (event: SQSEvent): { request?: IncomingRequest, error ?: ValidationError } => {
|
export const validateRequestEvent = (
|
||||||
|
event: SQSEvent
|
||||||
|
): { request?: IncomingRequest; error?: ValidationError } => {
|
||||||
try {
|
try {
|
||||||
const request = validateRequestBody(event.Records[0].body)
|
const request = validateRequestBody(event.Records[0].body)
|
||||||
validateRequestDateTimes(request.dateTimes)
|
validateRequestDateTimes(request.dateTimes)
|
||||||
validateRequestOpponent(request.opponent)
|
validateRequestOpponent(request.opponent)
|
||||||
return { request }
|
return { request }
|
||||||
} catch (err: any) {
|
} catch (err: unknown) {
|
||||||
return { error: { message: 'Invalid request', code: err.code ?? 0 } }
|
return { error: { message: 'Invalid request', code: (err as ValidationError).code ?? 0 } }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -32,7 +30,7 @@ const validateRequestBody = (body?: string): IncomingRequest => {
|
||||||
if (body === undefined) {
|
if (body === undefined) {
|
||||||
throw {
|
throw {
|
||||||
message: 'Invalid request',
|
message: 'Invalid request',
|
||||||
code: 1
|
code: 1,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -46,12 +44,14 @@ const validateRequestBody = (body?: string): IncomingRequest => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const {
|
const { username, password, dateTimes } = jsonBody
|
||||||
username,
|
if (
|
||||||
password,
|
!username ||
|
||||||
dateTimes,
|
username.length < 1 ||
|
||||||
} = jsonBody
|
!password ||
|
||||||
if (!username || username.length < 1 || !password || password.length < 1 || !dateTimes) {
|
password.length < 1 ||
|
||||||
|
!dateTimes
|
||||||
|
) {
|
||||||
throw {
|
throw {
|
||||||
message: 'Invalid request',
|
message: 'Invalid request',
|
||||||
code: 3,
|
code: 3,
|
||||||
|
|
@ -65,23 +65,17 @@ const validateRequestDateTimes = (dateTimes: DateTime[]): void => {
|
||||||
const now = new Date()
|
const now = new Date()
|
||||||
for (let i = 0; i < dateTimes.length; i++) {
|
for (let i = 0; i < dateTimes.length; i++) {
|
||||||
const dateTime = dateTimes[i]
|
const dateTime = dateTimes[i]
|
||||||
const {
|
const { year, month, day, timeRange } = dateTime
|
||||||
year,
|
const { start, end } = timeRange
|
||||||
month,
|
|
||||||
day,
|
|
||||||
timeRange
|
|
||||||
} = dateTime
|
|
||||||
const {
|
|
||||||
start,
|
|
||||||
end
|
|
||||||
} = timeRange
|
|
||||||
|
|
||||||
if (
|
if (
|
||||||
typeof year !== 'number' ||
|
typeof year !== 'number' ||
|
||||||
typeof month !== 'number' ||
|
typeof month !== 'number' ||
|
||||||
typeof day !== 'number' ||
|
typeof day !== 'number' ||
|
||||||
typeof start.hour !== 'number' || typeof start.minute !== 'number' ||
|
typeof start.hour !== 'number' ||
|
||||||
typeof end.hour !== 'number' || typeof end.minute !== 'number'
|
typeof start.minute !== 'number' ||
|
||||||
|
typeof end.hour !== 'number' ||
|
||||||
|
typeof end.minute !== 'number'
|
||||||
) {
|
) {
|
||||||
throw {
|
throw {
|
||||||
message: 'Invalid request',
|
message: 'Invalid request',
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
export class Reservation {
|
||||||
public readonly dateTime: DateTime
|
public readonly dateTime: DateTime
|
||||||
public readonly opponent: Opponent
|
public readonly opponent: Opponent
|
||||||
public readonly possibleTimes: Time[]
|
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.dateTime = dateTime
|
||||||
this.opponent = opponent
|
this.opponent = opponent
|
||||||
this.possibleTimes = this.createPossibleTimes()
|
this.possibleTimes = this.createPossibleTimes()
|
||||||
|
|
@ -38,7 +39,7 @@ export class Reservation {
|
||||||
const { start, end } = this.dateTime.timeRange
|
const { start, end } = this.dateTime.timeRange
|
||||||
|
|
||||||
let { hour, minute } = start
|
let { hour, minute } = start
|
||||||
let { hour: endHour, minute: endMinute } = end
|
const { hour: endHour, minute: endMinute } = end
|
||||||
|
|
||||||
while (hour <= endHour && minute <= endMinute) {
|
while (hour <= endHour && minute <= endMinute) {
|
||||||
possibleTimes.push({ hour, minute })
|
possibleTimes.push({ hour, minute })
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,12 @@
|
||||||
import puppeteer, { Browser, BrowserConnectOptions, BrowserLaunchArgumentOptions, ElementHandle, LaunchOptions, Page } from "puppeteer"
|
import puppeteer, {
|
||||||
import { DateTime, Opponent, Reservation, timeToString } from "./reservation";
|
Browser,
|
||||||
|
BrowserConnectOptions,
|
||||||
|
BrowserLaunchArgumentOptions,
|
||||||
|
ElementHandle,
|
||||||
|
LaunchOptions,
|
||||||
|
Page,
|
||||||
|
} from 'puppeteer'
|
||||||
|
import { DateTime, Opponent, Reservation, timeToString } from './reservation'
|
||||||
|
|
||||||
export class Runner {
|
export class Runner {
|
||||||
private readonly username: string
|
private readonly username: string
|
||||||
|
|
@ -9,13 +16,21 @@ export class Runner {
|
||||||
private browser: Browser | undefined
|
private browser: Browser | undefined
|
||||||
private page: Page | 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.username = username
|
||||||
this.password = password
|
this.password = password
|
||||||
this.reservations = reservations
|
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.browser = await puppeteer.launch(options)
|
||||||
this.page = await this.browser.newPage()
|
this.page = await this.browser.newPage()
|
||||||
await this.login()
|
await this.login()
|
||||||
|
|
@ -23,10 +38,14 @@ export class Runner {
|
||||||
}
|
}
|
||||||
|
|
||||||
private async login() {
|
private async login() {
|
||||||
await this.page!.goto('https://squashcity.baanreserveren.nl/')
|
await this.page?.goto('https://squashcity.baanreserveren.nl/')
|
||||||
await this.page!.waitForSelector('input[name=username]').then(i => i!.type(this.username))
|
await this.page
|
||||||
await this.page!.$('input[name=password]').then(i => i!.type(this.password))
|
?.waitForSelector('input[name=username]')
|
||||||
await this.page!.$('button').then((b) => b!.click())
|
.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[]> {
|
private async makeReservations(): Promise<Reservation[]> {
|
||||||
|
|
@ -50,37 +69,48 @@ export class Runner {
|
||||||
}
|
}
|
||||||
|
|
||||||
private async navigateToDay(dt: DateTime): Promise<void> {
|
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
|
||||||
await this.page!.waitForSelector(`td#cal_${dt.year}_${dt.month}_${dt.day}.selected`)
|
?.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> {
|
private async selectAvailableTime(res: Reservation): Promise<void> {
|
||||||
let freeCourt: ElementHandle | null = null
|
let freeCourt: ElementHandle | null | undefined
|
||||||
let i = 0
|
let i = 0
|
||||||
while (i < res.possibleTimes.length && freeCourt !== null) {
|
while (i < res.possibleTimes.length && !freeCourt) {
|
||||||
const possibleTime = res.possibleTimes[i]
|
const possibleTime = res.possibleTimes[i]
|
||||||
const timeString = timeToString(possibleTime)
|
const timeString = timeToString(possibleTime)
|
||||||
const selector = `tr[data-time='${timeString}']` +
|
const selector =
|
||||||
`> td.free[rowspan='3'][type='free']`
|
`tr[data-time='${timeString}']` + `> td.free[rowspan='3'][type='free']`
|
||||||
freeCourt = await this.page!.$(selector)
|
freeCourt = await this.page?.$(selector)
|
||||||
|
i++
|
||||||
}
|
}
|
||||||
|
|
||||||
if (freeCourt === null) {
|
if (!freeCourt) {
|
||||||
throw new Error("No free court available")
|
throw new Error('No free court available')
|
||||||
}
|
}
|
||||||
|
|
||||||
await freeCourt.click()
|
await freeCourt.click()
|
||||||
}
|
}
|
||||||
|
|
||||||
private async selectOpponent(opponent: Opponent): Promise<void> {
|
private async selectOpponent(opponent: Opponent): Promise<void> {
|
||||||
const player2Search = await this.page!.waitForSelector('tr.res-make-player-2 > td > input')
|
const player2Search = await this.page?.waitForSelector(
|
||||||
await player2Search!.type(opponent.name)
|
'tr.res-make-player-2 > td > input'
|
||||||
await this.page!.waitForNetworkIdle()
|
)
|
||||||
await this.page!.$('select.br-user-select[name="players[2]"]').then(d => d!.select(opponent.id))
|
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> {
|
private async confirmReservation(): Promise<void> {
|
||||||
await this.page!.$('input#__make_submit').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())
|
await this.page
|
||||||
|
?.waitForSelector('input#__make_submit2')
|
||||||
|
.then((b) => b?.click())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Loading…
Add table
Reference in a new issue