fix(favorites): use cursor-based pagination instead of page numbers
FA's /favorites/{user}/ pagination is cursor-addressed by the fave-ID
of the last item on the previous page (e.g.
/favorites/{user}/1951234825/next), not by sequential integers. The
previous URL builder generated /favorites/{user}/{N}/ for N>=2; FA
interpreted that as a malformed cursor and silently returned page 1,
which caused the Favorites iterator to loop forever and the new
FavoritesPage to report HasNext=true on every call.
Changes:
- urls.Favorites(name) returns the first-page URL; new
urls.FavoritesCursor(name, cursor) builds /favorites/.../next URLs.
- FavoritesPage now takes a cursor string; empty = first page.
Returns ListingPage.NextPage as the opaque fave-ID for the next call.
- ListingPage gains NextPage string (decimal page number for
Gallery/Scraps, fave-ID cursor for Favorites) and drops the Page int
field that conflated those two notions.
- Client.Favorites iterator now walks cursors internally; StartPage
is ignored for favorites (documented).
- detectNextPage / nextPageURL now parse the form action so the same
helper works for both page-number and cursor pagination.
- Added regression test that fails on the infinite-loop bug.
- Example: examples/favorites_page demonstrates cursor walking.
This commit is contained in:
79
examples/favorites_page/main.go
Normal file
79
examples/favorites_page/main.go
Normal file
@@ -0,0 +1,79 @@
|
||||
// favorites_page exercises the per-page favorites listing API
|
||||
// ([Client.FavoritesPage]) against the live FA site so a caller can see
|
||||
// exactly what fields come back: HasNext, NextPage, len(Items), and a
|
||||
// sample of the tag data lifted from each figure's data-tags attribute.
|
||||
//
|
||||
// Favorites pagination is cursor-based: each page returns an opaque
|
||||
// NextPage token that addresses the next page. Pass it back in on the
|
||||
// next call; treat empty as end-of-pagination.
|
||||
//
|
||||
// Required environment variables:
|
||||
//
|
||||
// FA_A — the `a` session cookie
|
||||
// FA_B — the `b` session cookie
|
||||
// CF_CLEARANCE — (optional) cf_clearance cookie if Cloudflare challenges
|
||||
// FA_UA — (optional) User-Agent matching CF_CLEARANCE
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// go run ./examples/favorites_page <username> [maxPages]
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"strconv"
|
||||
|
||||
fa "git.anthrove.art/public/go-fa-api"
|
||||
)
|
||||
|
||||
func main() {
|
||||
if len(os.Args) < 2 {
|
||||
log.Fatalf("usage: %s <username> [maxPages]", os.Args[0])
|
||||
}
|
||||
user := os.Args[1]
|
||||
maxPages := 0
|
||||
if len(os.Args) >= 3 {
|
||||
if n, err := strconv.Atoi(os.Args[2]); err == nil && n > 0 {
|
||||
maxPages = n
|
||||
}
|
||||
}
|
||||
|
||||
opts := []fa.Option{
|
||||
fa.WithCookies(fa.Cookies{A: os.Getenv("FA_A"), B: os.Getenv("FA_B")}),
|
||||
}
|
||||
if cf := os.Getenv("CF_CLEARANCE"); cf != "" {
|
||||
opts = append(opts, fa.WithCloudflare(fa.CFCookies{Clearance: cf}))
|
||||
}
|
||||
if ua := os.Getenv("FA_UA"); ua != "" {
|
||||
opts = append(opts, fa.WithUserAgent(ua))
|
||||
}
|
||||
client := fa.New(opts...)
|
||||
|
||||
cursor := ""
|
||||
pageNum := 0
|
||||
for {
|
||||
pageNum++
|
||||
lp, err := client.FavoritesPage(context.Background(), user, cursor)
|
||||
if err != nil {
|
||||
log.Fatalf("FavoritesPage(cursor=%q): %v", cursor, err)
|
||||
}
|
||||
fmt.Printf("=== page %d cursor=%q items=%d HasNext=%v NextPage=%q ===\n",
|
||||
pageNum, cursor, len(lp.Items), lp.HasNext, lp.NextPage)
|
||||
for i, sub := range lp.Items {
|
||||
fmt.Printf(" [%d] id=%d rating=%s author=%s title=%q\n",
|
||||
i, sub.ID, sub.Rating, sub.Author.Name, sub.Title)
|
||||
}
|
||||
if !lp.HasNext {
|
||||
fmt.Printf("\nreached end of pagination after %d page(s)\n", pageNum)
|
||||
return
|
||||
}
|
||||
if maxPages > 0 && pageNum >= maxPages {
|
||||
fmt.Printf("\nstopped at maxPages=%d (HasNext was still true; next cursor=%q)\n", maxPages, lp.NextPage)
|
||||
return
|
||||
}
|
||||
cursor = lp.NextPage
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user