The notes listing renders each thread link as
/msg/pms/{folder}/{noteID}/#message. extractIntFromHref returned the
first numeric segment it found, which was always the folder index (1
for the inbox), so every NotePreview.ID came out as 1 and any
follow-up GetNote(np.ID) call failed with "this message has either
been deleted or is not yours".
Surfaced by an end-to-end smoke run against the live site. Limited
to the notes parser; the other extractIntFromHref callers
(/view/{id}/, /journal/{id}/) only ever have a single numeric segment
so they are unaffected.
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.
GalleryPage / ScrapsPage / FavoritesPage return a ListingPage struct
carrying the page items, the 1-based page number, and a HasNext flag
that mirrors FA's "next page" link. This lets external scrapers drive
their own pagination loop (checkpoint resume, parallel workers,
custom throttling) without re-implementing the page-walking code.
The existing iter.Seq2-shaped methods now share the same per-page
primitive internally so behaviour stays in lock-step.
FA's beta listing pages emit each submission's tag list on the
figure's <img data-tags="..."> attribute, mixing prefixed system tags
(s_/c_/a_/u_/t_) with the unprefixed keyword list. Reading it during
gallery-page parse lets callers classify favorites/gallery/scraps/
browse/search/inbox items at scrape time, avoiding a /view/{id}
round-trip per submission.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
scripts/refresh-user-fixture.sh wraps the TestRefreshFixtures
invocation: validates required env vars, warns on missing CF
credentials, runs the refresh, and re-runs the user parser tests
against the new fixture.
Asserts exactly one of fav/unfav is set with a well-formed URL,
instead of hardcoding the +Fav direction. The previous test broke
whenever the capturing account favourited the target submission.
Also points the test at submission 12345678 (the documented default
FA_TEST_SUB_ID) so it matches what TestRefreshFixtures captures by
default.
Picks up testdata/html files that were previously skipped by
TestRefreshFixtures because their optional env vars were unset:
gallery_page_last, search_results, note_view, plus a fresh
submission.html / gallery_page1 captured against the right targets.
Re-captures testdata/html/*.html against the live site with valid
session cookies; the previous user.html was the logged-out interstitial,
which broke TestParseUser_RealFixture entirely. Bumps the expected
Stats.Views in that test to match the new fixture.
FA renders its species/character/artist/type system tags as tag-block
anchors with a data-tag-name carrying a single-letter prefix
(s_/c_/a_-u_/t_) and a sibling tag-invalid span instead of a /search/
link. The existing keyword pass skips them, so they were lost.
Adds a Submission.CategorizedTags field exposing the four buckets with
the prefix stripped, plus an examples/categorized_tags runnable demo.