import { Page } from "puppeteer-core" ; import Debug from "debug" ; const debug = Debug ( "impftermin:appointments" ) ; param page param maxWaitingTime returns async function sitInWaitingRoom ( page , maxWaitingTime ) { const isInWaitingRoom = async ( ) => { const title$ = await page . $ ( "h1" ) ; if ( title$ ) { const titleText = await ( await title$ . getProperty ( "innerText" ) ) ?. jsonValue < string > ( ) ; if ( titleText && titleText . includes ( "Warteraum" ) ) { return true ; } } return false ; } ; const inWaitingRoomTimestamp = Date . now ( ) ; while ( true ) { await page . waitForNavigation ( { waitUntil : "networkidle0" , timeout : 1000 * 60 * 10 , } ) ; if ( ! ( await isInWaitingRoom ( ) ) ) { debug ( "We are no longer in the waiting room" ) ; break ; } const waitingTime = Date . now ( ) - inWaitingRoomTimestamp ; debug ( `We have been waiting for ${ Math . round ( waitingTime / 1000 ) } seconds.` ) ; if ( waitingTime > maxWaitingTime ) { return false ; } } } async function acceptNecessaryCookies ( page ) { try { await page . click ( ".cookies-info-close" ) ; debug ( "Accepted necessary cookies" ) ; } catch { } await page . waitForTimeout ( 1000 ) ; } async function proceedWithACode ( page , impfCode ) { debug ( "Checking with existing code " + impfCode ) ; for ( const listElement of await page . $$ ( "span" ) ) { const idName = await ( await listElement . getProperty ( "innerText" ) ) ?. jsonValue < string > ( ) ; if ( idName && idName . includes ( "Ja" ) ) { await listElement . click ( { delay : 200 } ) ; break ; } } await page . waitForTimeout ( 1000 ) ; await page . type ( "input[name='ets-input-code-0']" , impfCode ) ; await page . waitForTimeout ( 3000 ) ; const submitButton = await page . $ ( "button[type='submit']" ) ; await submitButton ?. click ( { delay : 200 } ) ; await page . waitForNavigation ( { waitUntil : "networkidle0" , } ) ; await page . waitForTimeout ( 3000 ) ; const codeAlreadyInUseHeader = await page . $ ( "h2.ets-booking-headline" ) ; const codeAlreadyInUseText = await ( await codeAlreadyInUseHeader ?. getProperty ( "innerText" ) ) ?. jsonValue < string > ( ) ; if ( codeAlreadyInUseText && codeAlreadyInUseText . toLowerCase ( ) . includes ( "ihr termin" ) ) { debug ( "There is already a booked appointment with code %s" , impfCode ) ; return false ; } const searchButton = await page . $ ( "button.search-filter-button" ) ; await searchButton ?. click ( { delay : 500 } ) ; await page . waitForTimeout ( 2000 ) ; const appointmentWarning = await page . $ ( "span.its-slot-pair-search-no-results" ) ; const appointmentText = await ( await appointmentWarning ?. getProperty ( "innerText" ) ) ?. jsonValue < string > ( ) ; if ( await areWeOffline ( page ) ) { debug ( "We are offline or on some different page" ) ; return false ; } for ( const listElementRealOffer of await page . $$ ( "span" ) ) { const elementText = await ( await listElementRealOffer . getProperty ( "innerText" ) ) ?. jsonValue < string > ( ) ; if ( elementText && elementText . includes ( "Uhr" ) ) { debug ( "Appointments available!!" ) ; debug ( "DEBUG: appointmentText=%s" , appointmentText ) ; return true ; } } debug ( "No appointments" ) ; debug ( "DEBUG: appointmentText=%s" , appointmentText ) ; return false ; } export async function proceedWithoutACode ( page ) { debug ( "Checking without a code" ) ; for ( const listElement of await page . $$ ( "span" ) ) { const idName = await ( await listElement . getProperty ( "innerText" ) ) ?. jsonValue < string > ( ) ; if ( idName && idName . includes ( "Nein" ) ) { await listElement . click ( { delay : 200 } ) ; break ; } } debug ( "Checking for loading message" ) ; while ( await isLoadingAvailableCodes ( page ) ) { debug ( "Waiting 1s for loading message to be gone" ) ; await page . waitForTimeout ( 1000 ) ; } debug ( "Loading message is gone" ) ; const appointmentWarning = await page . $ ( "div.alert.alert-danger" ) ; if ( await areWeOffline ( page ) ) { debug ( "We are offline or on some different page" ) ; return false ; } if ( ! appointmentWarning ) { debug ( "Appointments available!!" ) ; return true ; } debug ( "No appointments" ) ; return false ; } async function areWeOffline ( page ) { return ! ( await page . $ ( "div.footer-copyright" ) ) ; } export async function checkForAppointments ( page , impfLocationUrl , maxWaitingRoomTimeBeforeRefresh , impfCode ) { do { await page . goto ( impfLocationUrl ) ; debug ( "Checking impf location " + impfLocationUrl ) ; } while ( await sitInWaitingRoom ( page , maxWaitingRoomTimeBeforeRefresh ) ) ; await acceptNecessaryCookies ( page ) ; if ( impfCode ) { return await proceedWithACode ( page , impfCode ) ; } else { return await proceedWithoutACode ( page ) ; } } async function isLoadingAvailableCodes ( page ) { const searchIcon = await page . $ ( ".icon.icon-search" ) ; if ( searchIcon !== null ) { return true ; } const appointmentLoadingForm = await page . $ ( "div.ets-login-form-section.in" ) ; if ( appointmentLoadingForm == null ) { debug ( "The page element 'div.ets-login-form-section.in' was not found. If there is a notification below, it might be a false positive." ) ; return false ; } const text = await appointmentLoadingForm . evaluate ( ( el ) => el . textContent ) ; const textMatches = text . match ( /Bitte warten, wir suchen verfügbare Termine in Ihrer Region/ ) ; return textMatches != null ; }