feat(listing): populate Tags and CategorizedTags from figure data-tags

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>
This commit is contained in:
2026-06-02 21:53:56 +02:00
parent bc2d27f702
commit a2fc1b7e32
3 changed files with 146 additions and 3 deletions

View File

@@ -85,6 +85,15 @@ func parseGalleryFigure(sel *goquery.Selection, jsonData listingJSONMap) *Submis
}
}
// data-tags on the figure's <img> carries both the unprefixed keyword
// list and the prefixed system tags (s_/c_/a_/u_/t_). Splitting it lets
// callers classify listing items without an extra /view/ fetch.
if img := sel.Find("img[data-tags]").First(); img.Length() > 0 {
if raw, ok := img.Attr("data-tags"); ok {
applyListingDataTags(s, raw)
}
}
// JSON enrichment preferred sources for the fields it carries.
if jsonData != nil {
if entry, ok := jsonData[id]; ok {
@@ -105,3 +114,35 @@ func parseGalleryFigure(sel *goquery.Selection, jsonData listingJSONMap) *Submis
return s
}
// applyListingDataTags splits the whitespace-separated data-tags attribute
// FA emits on listing-page <img> elements and routes each token to either
// CategorizedTags (when the token has a known single-letter prefix
// s_/c_/a_/u_/t_) or Tags (everything else).
//
// The prefix mapping mirrors the /view/ parser in submission_parser.go so a
// listing-path Submission carries the same categorisation a /view/-path one
// would, modulo tokens FA can't represent in this flat attribute (multi-word
// tags, the a_ vs u_ distinction).
func applyListingDataTags(s *Submission, raw string) {
for _, tok := range strings.Fields(raw) {
if len(tok) >= 3 && tok[1] == '_' {
name := tok[2:]
switch tok[0] {
case 's':
s.CategorizedTags.Species = append(s.CategorizedTags.Species, name)
continue
case 'c':
s.CategorizedTags.Characters = append(s.CategorizedTags.Characters, name)
continue
case 'a', 'u':
s.CategorizedTags.Artists = append(s.CategorizedTags.Artists, name)
continue
case 't':
s.CategorizedTags.Types = append(s.CategorizedTags.Types, name)
continue
}
}
s.Tags = append(s.Tags, tok)
}
}