diff --git a/main.go b/main.go index 429ea2a..d28f64e 100644 --- a/main.go +++ b/main.go @@ -278,67 +278,44 @@ func (s *Session) firstNav(ctx context.Context) error { return nil } -// navToEnd waits for the page to be ready to receive scroll key events, -// and then scrolls down to the end of the page, i.e. to the oldest items. +// navToEnd selects the last item in the page +// by repeatedly advances the selected item with +// - kb.ArrowRight (which causes an initial selection, and/or advances it by one) +// - kb.End which scrolls to the end of the page, and advances the selected item. +// Note timing is important, because when the kb.End causes significant scrolling, +// the active element become undefined for a certain time, in that case, we get an error (ignore), sleep, and retry. +// The termnation criteria is that the selected item (document.activeElement.href) is stable for >2 iterations func navToEnd(ctx context.Context) error { - // wait for page to be loaded - chromedp.WaitReady("body", chromedp.ByQuery).Do(ctx) - - // Try scrolling to the end of the page. - // After each scroll attempt we extract the last Photo Page Element (href) from the DOM. - // We detect we are at the end when two consecutive DOM extractions are identical. - var previousHref string + var prev, active string + lastRepeated := 0 for { - chromedp.KeyEvent(kb.PageDown).Do(ctx) + chromedp.KeyEvent(kb.ArrowRight).Do(ctx) chromedp.KeyEvent(kb.End).Do(ctx) - time.Sleep(tick) // sleep *before* we establish new state in DOM + time.Sleep(tick) - // Extract last Photo Page Element (href) from DOM. - lastHrefInDOM, err := lastPhotoInDOM(ctx) - if err != nil { - continue // Just ignore this error, continue will retry. + if err := chromedp.Evaluate(`document.activeElement.href`, &active).Do(ctx); err != nil { + time.Sleep(tick) // this extra sleep is important: after the kb.End, it sometimes takes a while for the active element to be reset + continue // ignore this error: no active element, or active element has no href + } + if active == prev { + lastRepeated++ + } else { + lastRepeated = 0 } - if previousHref == lastHrefInDOM { + if *verboseFlag { + log.Printf("** navToEnd:activeElt %s %d", active, lastRepeated) + } + if lastRepeated > 2 { break } - previousHref = lastHrefInDOM - } - - // Now that we have stopped scrolling, select (focus) on the last element - // The element must be focused, so that navToLast can send "\n" to enter photo detail page - lastEltSel := fmt.Sprintf(`a[href="%s"]`, previousHref) - if err := chromedp.Focus(lastEltSel).Do(ctx); err != nil { - return err + prev = active + // time.Sleep(tick) } - if *verboseFlag { - log.Printf("Successfully jumped to the end: %s", previousHref) + log.Printf("Successfully jumped to the end: %s", active) } - return nil -} -// When in the Main/Album Page, the DOM contains elements for all visible images. -// lastPhotoInDOM simply returns the last such href in document order. -// The DOM actually contains more images than those that are visible, in a kind of virtual scrolling window -// In the DOM, but not reflecting exactly the visible photos (actually a superset of the visible elements): -// -// -// -// We tried to find the actual *oldest* photo by using the aria-label attribute which contains a date for the photo, -// unfortunately that label is localised for each user's language which makes the date format very hard to parse. -func lastPhotoInDOM(ctx context.Context) (string, error) { - sel := `a[href^="./photo/"]` // css selector for all links to images with href prefix "./photo/..." - var attrs []map[string]string - if err := chromedp.AttributesAll(sel, &attrs).Do(ctx); err != nil { - return "", err - } - if len(attrs) == 0 { - return "", fmt.Errorf("lastPhotoInDOM: no elements match") - } - - lastElement := attrs[len(attrs)-1] - href := lastElement["href"] - return href, nil + return nil } // navToLast sends the "\n" event until we detect that an item is loaded as a