inital commit
This commit is contained in:
68
listing_json.go
Normal file
68
listing_json.go
Normal file
@@ -0,0 +1,68 @@
|
||||
package fa
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"strings"
|
||||
|
||||
"github.com/PuerkitoBio/goquery"
|
||||
)
|
||||
|
||||
// listingJSONEntry is one row of FA's embedded js-submissionData blob —
|
||||
// a JSON dictionary every listing page emits with stable metadata for
|
||||
// each visible submission. The blob is keyed by submission ID (string)
|
||||
// and survives most HTML reshuffles because it's consumed by FA's own
|
||||
// front-end JS.
|
||||
//
|
||||
// Not every field appears on every page; description is sometimes truncated
|
||||
// to ~500 chars with a trailing "…". We use this purely as a hinting
|
||||
// source the HTML figure scrape remains authoritative for fields the
|
||||
// JSON doesn't carry (ID, rating, thumbnail).
|
||||
type listingJSONEntry struct {
|
||||
Title string `json:"title"`
|
||||
Description string `json:"description"` // BBCode source (may be truncated)
|
||||
Username string `json:"username"` // display name with original casing
|
||||
Lower string `json:"lower"` // URL-safe lowercase login
|
||||
AvatarMtime string `json:"avatar_mtime"`
|
||||
}
|
||||
|
||||
// listingJSONMap is the parsed js-submissionData blob: SubmissionID → entry.
|
||||
type listingJSONMap map[SubmissionID]listingJSONEntry
|
||||
|
||||
// readListingJSON pulls and parses the <script id="js-submissionData">
|
||||
// blob from doc. Returns nil if the tag is absent or the JSON is
|
||||
// malformed the caller then falls back to HTML-only scraping. The
|
||||
// JSON is keyed by string in the source; we deserialize directly into a
|
||||
// SubmissionID map by exploiting the typed int unmarshal path.
|
||||
func readListingJSON(doc *goquery.Document) listingJSONMap {
|
||||
raw := strings.TrimSpace(doc.Find("script#js-submissionData").First().Text())
|
||||
if raw == "" {
|
||||
return nil
|
||||
}
|
||||
// Decode into string-keyed map first because Go's json package only
|
||||
// accepts string-convertible keys from JSON object keys.
|
||||
var stringKeyed map[string]listingJSONEntry
|
||||
if err := json.Unmarshal([]byte(raw), &stringKeyed); err != nil {
|
||||
return nil
|
||||
}
|
||||
out := make(listingJSONMap, len(stringKeyed))
|
||||
for k, v := range stringKeyed {
|
||||
id, err := parseID[SubmissionID](k)
|
||||
if err != nil || id == 0 {
|
||||
continue
|
||||
}
|
||||
out[id] = v
|
||||
}
|
||||
if len(out) == 0 {
|
||||
return nil
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// avatarURLFromMtime builds FA's avatar CDN URL from the mtime + lowercase
|
||||
// login. Returns "" when either piece is missing.
|
||||
func avatarURLFromMtime(lower, mtime string) string {
|
||||
if lower == "" || mtime == "" {
|
||||
return ""
|
||||
}
|
||||
return "https://a.furaffinity.net/" + mtime + "/" + lower + ".gif"
|
||||
}
|
||||
Reference in New Issue
Block a user