|
|
@ -84,7 +84,8 @@ func (env *Env) BackgroundJobRunner() { |
|
|
|
} else { // if there's job to do
|
|
|
|
// Create next run where it's not the past (either from old NextRun or now())
|
|
|
|
timeNow := time.Now() |
|
|
|
startTime := time.Date(timeNow.Year(), timeNow.Month(), timeNow.Day(), 00, 10, 0, 0, timeNow.Location()) |
|
|
|
// Run at 12:13am
|
|
|
|
startTime := time.Date(timeNow.Year(), timeNow.Month(), timeNow.Day(), 00, 13, 0, 0, timeNow.Location()) |
|
|
|
endTime := startTime.Add(15 * time.Minute) |
|
|
|
|
|
|
|
if forceStartBooking || (timeNow.After(startTime) && timeNow.Before(endTime)) { |
|
|
@ -232,7 +233,8 @@ func (env *Env) startBooking(job *Booking, username string, password string, cre |
|
|
|
currPage := getBookingSlots(browser, onwardDate, reverse) |
|
|
|
log.Println("Booking page loaded.") |
|
|
|
|
|
|
|
currPage = selectBookingSlot(getBookingSlotCtx, currPage, timeCode) |
|
|
|
delay := int(-5 + i) |
|
|
|
currPage = selectBookingSlot(getBookingSlotCtx, currPage, timeCode, delay) |
|
|
|
log.Println("Booking slot selected.") |
|
|
|
|
|
|
|
// Make sure page completes loading
|
|
|
@ -290,7 +292,10 @@ func (env *Env) startBooking(job *Booking, username string, password string, cre |
|
|
|
|
|
|
|
<-filledPassengerDetailContext.Done() |
|
|
|
|
|
|
|
passthroughLaggingPage = chooseAndMakePayment(timerCtx, creditCardType, creditCard, creditCardCVV, creditCardExpiry, browser, page) |
|
|
|
envSkipMakePayment := os.Getenv("SKIP_MAKE_PAYMENT") |
|
|
|
if strings.ToUpper(envSkipMakePayment) != "TRUE" { |
|
|
|
passthroughLaggingPage = chooseAndMakePayment(timerCtx, creditCardType, creditCard, creditCardCVV, creditCardExpiry, browser, page) |
|
|
|
} |
|
|
|
|
|
|
|
// Exits if context cancelled
|
|
|
|
select { |
|
|
@ -430,8 +435,7 @@ func getBookingSlots(browser *rod.Browser, onwardDate string, reverse bool) *rod |
|
|
|
return page |
|
|
|
} |
|
|
|
|
|
|
|
func selectBookingSlot(ctx context.Context, page *rod.Page, timeCode string) *rod.Page { |
|
|
|
time.Sleep(5 * time.Second) |
|
|
|
func selectBookingSlot(ctx context.Context, page *rod.Page, timeCode string, delay int) *rod.Page { |
|
|
|
twoCaptchaAPIKey := os.Getenv("TWOCAPTCHA_API_KEY") |
|
|
|
|
|
|
|
needToWait := false |
|
|
@ -450,10 +454,10 @@ func selectBookingSlot(ctx context.Context, page *rod.Page, timeCode string) *ro |
|
|
|
// Start probing
|
|
|
|
reportedTicketDetails := false |
|
|
|
completed := false |
|
|
|
retries := 2 |
|
|
|
retries := 3 |
|
|
|
for !completed { |
|
|
|
if retries <= 0 { |
|
|
|
log.Println("Used up retries. Exiting.") |
|
|
|
log.Printf("[%d] Used up retries. Exiting.", delay) |
|
|
|
completed = true |
|
|
|
break |
|
|
|
} |
|
|
@ -478,7 +482,7 @@ func selectBookingSlot(ctx context.Context, page *rod.Page, timeCode string) *ro |
|
|
|
} |
|
|
|
|
|
|
|
if rowElement == nil { |
|
|
|
log.Println("No timeslot found. Waiting till context cancelled.") |
|
|
|
log.Printf("[%d] No timeslot found. Waiting till context cancelled.", delay) |
|
|
|
<-ctx.Done() |
|
|
|
return nil |
|
|
|
} |
|
|
@ -498,14 +502,15 @@ func selectBookingSlot(ctx context.Context, page *rod.Page, timeCode string) *ro |
|
|
|
|
|
|
|
// Wait for 1214am
|
|
|
|
if needToWait { |
|
|
|
log.Println("Waiting for 1214am") |
|
|
|
log.Printf("[%d] Waiting for 1214am", delay) |
|
|
|
destinationTime := time.Date(time.Now().Year(), time.Now().Month(), time.Now().Day(), 0, 14, 0, 0, time.Now().Location()) |
|
|
|
destinationTime = destinationTime.Add(time.Duration(delay) * time.Second) |
|
|
|
needToWaitTimer := time.NewTimer(time.Until(destinationTime)) |
|
|
|
select { |
|
|
|
case <-ctx.Done(): |
|
|
|
return page |
|
|
|
case <-needToWaitTimer.C: |
|
|
|
log.Println("1214am reached") |
|
|
|
log.Printf("[%d] 1214am reached", delay) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
@ -514,13 +519,14 @@ func selectBookingSlot(ctx context.Context, page *rod.Page, timeCode string) *ro |
|
|
|
|
|
|
|
page.MustWaitLoad() |
|
|
|
time.Sleep(500 * time.Millisecond) |
|
|
|
bodyText = page.MustElement("body").MustText() |
|
|
|
|
|
|
|
// Check if there is captcha
|
|
|
|
if strings.Contains(bodyText, "Please complete the reCAPTCHA") { |
|
|
|
if strings.Contains(strings.ToLower(bodyText), "please complete the recaptcha") { |
|
|
|
// Reset Body text
|
|
|
|
time.Sleep(500 * time.Millisecond) |
|
|
|
|
|
|
|
log.Println("Captcha detected") |
|
|
|
log.Printf("[%d] Captcha detected", delay) |
|
|
|
currURL := "https://shuttleonline.ktmb.com.my/ShuttleTrip" |
|
|
|
// Regex research for Google Captcha V2 API Key
|
|
|
|
gcaptchaElement := page.MustElement(".g-recaptcha") |
|
|
@ -531,24 +537,28 @@ func selectBookingSlot(ctx context.Context, page *rod.Page, timeCode string) *ro |
|
|
|
|
|
|
|
// Wait for 1215am
|
|
|
|
if needToWait { |
|
|
|
log.Println("Waiting for 1215am") |
|
|
|
log.Printf("[%d] Waiting for 1215am", delay) |
|
|
|
destinationTime := time.Date(time.Now().Year(), time.Now().Month(), time.Now().Day(), 0, 15, 1, 0, time.Now().Location()) |
|
|
|
destinationTime = destinationTime.Add(time.Duration(delay) * time.Second) |
|
|
|
needToWaitTimer := time.NewTimer(time.Until(destinationTime)) |
|
|
|
select { |
|
|
|
case <-ctx.Done(): |
|
|
|
return page |
|
|
|
case <-needToWaitTimer.C: |
|
|
|
log.Println("1215am reached") |
|
|
|
log.Printf("[%d] 1215am reached", delay) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
page.Eval(`RecaptchaCallback()`) |
|
|
|
time.Sleep(500 * time.Millisecond) |
|
|
|
page.MustWaitLoad() |
|
|
|
log.Printf("[%d] Submitting captcha", delay) |
|
|
|
} |
|
|
|
|
|
|
|
// Check before exiting
|
|
|
|
bodyText := page.MustElement("body").MustText() |
|
|
|
bodyText = page.MustElement("body").MustText() |
|
|
|
if strings.Contains(bodyText, "System maintenance scheduled at 23:00 to 00:15 (UTC+8).") { |
|
|
|
log.Println("Maintenance modal detected on booking page after captcha") |
|
|
|
log.Printf("[%d] Maintenance modal detected on booking page after captcha", delay) |
|
|
|
completed = false |
|
|
|
|
|
|
|
closeModalButton := page.MustElement("#popupModalCloseButton") |
|
|
@ -561,7 +571,7 @@ func selectBookingSlot(ctx context.Context, page *rod.Page, timeCode string) *ro |
|
|
|
|
|
|
|
// Repeat if there's no seats -- seats might be released later
|
|
|
|
if strings.Contains(bodyText, "Not enough seat for onward trip") { |
|
|
|
log.Println("Not enough seat for onward trip") |
|
|
|
log.Printf("[%d] Not enough seat for onward trip", delay) |
|
|
|
completed = false |
|
|
|
|
|
|
|
closeModalButton := page.MustElement("#popupModalCloseButton") |
|
|
@ -576,7 +586,7 @@ func selectBookingSlot(ctx context.Context, page *rod.Page, timeCode string) *ro |
|
|
|
if styleAttribute == nil || !strings.Contains(*styleAttribute, "display: none") { |
|
|
|
completed = false |
|
|
|
} else { |
|
|
|
log.Println("Completed probing") |
|
|
|
log.Printf("[%d] Completed probing", delay) |
|
|
|
completed = true |
|
|
|
} |
|
|
|
|
|
|
|