// import { Dayjs } from 'dayjs' // import { Injectable } from '@nestjs/common' // import dayjs from '../common/dayjs' // import puppeteer, { // Browser, // BrowserConnectOptions, // BrowserLaunchArgumentOptions, // ElementHandle, // LaunchOptions, // Page, // } from 'puppeteer' // import { asyncLocalStorage as l, LoggableError } from '../common/logger' // import { Reservation } from '../reservations/model' // import { Opponent } from '../common/opponent' // enum SessionAction { // NoAction, // Logout, // Login, // } // interface RunnerSession { // username: string // loggedInAt: Dayjs // } // export class RunnerInstance { // private static runner: Runner // static getRunner(): Runner { // if (!RunnerInstance.runner) { // RunnerInstance.runner = new Runner({ // headless: true, // }) // } // return RunnerInstance.runner // } // } // @Injectable() // export class Runner { // private browser?: Browser // private page?: Page // private options?: LaunchOptions & // BrowserLaunchArgumentOptions & // BrowserConnectOptions // private session?: RunnerSession // constructor( // options?: LaunchOptions & // BrowserLaunchArgumentOptions & // BrowserConnectOptions // ) { // const defaultArgs = ['--disable-setuid-sandbox', '--no-sandbox'] // this.options = { args: defaultArgs, ...options } // } // public async test() { // l.getStore()?.debug('Runner test') // try { // if (!this.browser) { // this.browser = await puppeteer.launch(this.options) // } // } catch (error: unknown) { // l.getStore()?.error('Browser error', { error: (error as Error).message }) // throw new PuppeteerBrowserLaunchError(error as Error) // } // try { // this.page = await this.browser?.newPage() // } catch (error) { // l.getStore()?.error('Page error', { error: (error as Error).message }) // throw new PuppeteerNewPageError(error as Error) // } // } // public async run(reservations: Reservation[]) { // try { // if (!this.browser) { // l.getStore()?.debug('Launching browser') // this.browser = await puppeteer.launch(this.options) // } // } catch (error) { // throw new PuppeteerBrowserLaunchError(error as Error) // } // for (const reservation of reservations) { // await this.startSession(reservation) // await this.makeReservation(reservation) // } // } // private async checkSession(username: string): Promise { // if ( // this.page // ?.url() // .startsWith('https://squashcity.baanreserveren.nl/reservations') // ) { // l.getStore()?.info('Already logged in', { username }) // return this.session?.username !== username // ? SessionAction.Logout // : SessionAction.NoAction // } // return SessionAction.Login // } // private async startSession(reservation: Reservation) { // if (!this.page) { // try { // this.page = await this.browser?.newPage() // } catch (error) { // throw new PuppeteerNewPageError(error as Error) // } // } // this.page // ?.goto('https://squashcity.baanreserveren.nl/reservations') // .catch((e: Error) => { // throw new RunnerNewSessionError(e) // }) // const sessionAction = await this.checkSession(reservation.user.username) // switch (sessionAction) { // case SessionAction.Login: { // await this.login(reservation.user.username, reservation.user.password) // break // } // case SessionAction.Logout: { // await this.logout() // await this.login(reservation.user.username, reservation.user.password) // break // } // case SessionAction.NoAction: // default: // break // } // } // private async login(username: string, password: string) { // l.getStore()?.debug('Logging in', { username }) // await this.page // ?.waitForSelector('input[name=username]') // .then((i) => i?.type(username)) // .catch((e: Error) => { // throw new RunnerLoginUsernameInputError(e) // }) // await this.page // ?.$('input[name=password]') // .then((i) => i?.type(password)) // .catch((e: Error) => { // throw new RunnerLoginPasswordInputError(e) // }) // await this.page // ?.$('button') // .then((b) => b?.click()) // .catch((e: Error) => { // throw new RunnerLoginSubmitError(e) // }) // this.session = { // loggedInAt: dayjs(), // username, // } // } // private async logout() { // l.getStore()?.debug('Logging out', { username: this.session?.username }) // await this.page // ?.goto('https://squashcity.baanreserveren.nl/auth/logout') // .catch((e: Error) => { // throw new RunnerLogoutError(e) // }) // this.session = undefined // } // private async makeReservation(reservation: Reservation) { // await this.navigateToDay(reservation.dateRange.start) // await this.selectAvailableTime(reservation) // await this.selectOpponent(reservation.opponent) // await this.confirmReservation() // reservation.booked = true // } // private getLastVisibleDay(): Dayjs { // const lastDayOfMonth = dayjs().add(1, 'month').set('date', 0) // let daysToAdd = 0 // switch (lastDayOfMonth.day()) { // case 0: // daysToAdd = 0 // break // default: // daysToAdd = 7 - lastDayOfMonth.day() // break // } // return lastDayOfMonth.add(daysToAdd, 'day') // } // private async navigateToDay(date: Dayjs): Promise { // l.getStore()?.debug(`Navigating to ${date.format()}`) // if (this.getLastVisibleDay().isBefore(date)) { // l.getStore()?.debug('Date is on different page, increase month') // await this.page // ?.waitForSelector('td.month.next') // .then((d) => d?.click()) // .catch((e: Error) => { // throw new RunnerNavigationMonthError(e) // }) // } // await this.page // ?.waitForSelector( // `td#cal_${date.get('year')}_${date.get('month') + 1}_${date.get( // 'date' // )}` // ) // .then((d) => d?.click()) // .catch((e: Error) => { // throw new RunnerNavigationDayError(e) // }) // await this.page // ?.waitForSelector( // `td#cal_${date.get('year')}_${date.get('month') + 1}_${date.get( // 'date' // )}.selected` // ) // .catch((e: Error) => { // throw new RunnerNavigationSelectionError(e) // }) // } // private async selectAvailableTime(res: Reservation): Promise { // l.getStore()?.debug('Selecting available time', { // reservation: res.toString(true), // }) // let freeCourt: ElementHandle | null | undefined // let i = 0 // while (i < res.possibleDates.length && !freeCourt) { // const possibleDate = res.possibleDates[i] // const timeString = possibleDate.format('HH:mm') // const selector = // `tr[data-time='${timeString}']` + `> td.free[rowspan='3'][type='free']` // freeCourt = await this.page?.$(selector) // i++ // } // if (!freeCourt) { // throw new NoCourtAvailableError() // } // await freeCourt.click().catch((e: Error) => { // throw new RunnerCourtSelectionError(e) // }) // } // private async selectOpponent(opponent: Opponent): Promise { // l.getStore()?.debug('Selecting opponent', { opponent }) // const player2Search = await this.page // ?.waitForSelector('tr.res-make-player-2 > td > input') // .catch((e: Error) => { // throw new RunnerOpponentSearchError(e) // }) // await player2Search?.type(opponent.name).catch((e: Error) => { // throw new RunnerOpponentSearchInputError(e) // }) // await this.page?.waitForNetworkIdle().catch((e: Error) => { // throw new RunnerOpponentSearchNetworkError(e) // }) // await this.page // ?.$('select.br-user-select[name="players[2]"]') // .then((d) => d?.select(opponent.id)) // .catch((e: Error) => { // throw new RunnerOpponentSearchSelectionError(e) // }) // } // private async confirmReservation(): Promise { // await this.page // ?.$('input#__make_submit') // .then((b) => b?.click()) // .catch((e: Error) => { // throw new RunnerReservationConfirmButtonError(e) // }) // await this.page // ?.waitForSelector('input#__make_submit2') // .then((b) => b?.click()) // .catch((e: Error) => { // throw new RunnerReservationConfirmSubmitError(e) // }) // } // } // export class RunnerError extends LoggableError { // constructor(error: Error) { // super(error.message) // this.stack = error.stack // } // } // export class PuppeteerError extends RunnerError {} // export class PuppeteerBrowserLaunchError extends PuppeteerError {} // export class PuppeteerNewPageError extends PuppeteerError {} // export class RunnerNewSessionError extends RunnerError {} // export class RunnerLogoutError extends RunnerError {} // export class RunnerLoginNavigationError extends RunnerError {} // export class RunnerLoginUsernameInputError extends RunnerError {} // export class RunnerLoginPasswordInputError extends RunnerError {} // export class RunnerLoginSubmitError extends RunnerError {} // export class RunnerNavigationMonthError extends RunnerError {} // export class RunnerNavigationDayError extends RunnerError {} // export class RunnerNavigationSelectionError extends RunnerError {} // export class RunnerCourtSelectionError extends RunnerError {} // export class NoCourtAvailableError extends LoggableError {} // export class RunnerOpponentSearchError extends RunnerError {} // export class RunnerOpponentSearchInputError extends RunnerError {} // export class RunnerOpponentSearchNetworkError extends RunnerError {} // export class RunnerOpponentSearchSelectionError extends RunnerError {} // export class RunnerReservationConfirmButtonError extends RunnerError {} // export class RunnerReservationConfirmSubmitError extends RunnerError {}