Files
go-fa-api/internal/urls/routes.go
2026-05-25 22:27:18 +02:00

148 lines
4.5 KiB
Go

// Package urls is the single source of truth for every FA URL the SDK
// constructs. Centralising route building here keeps fragile path
// concatenation out of the public API and makes the site's URL scheme
// trivial to swap (e.g., were FA to move endpoints).
package urls
import (
"fmt"
"net/url"
"strconv"
"strings"
)
// Host is the canonical FA host. Exported so callers can override for
// proxies or local mirrors, but the production value is what every
// builder below uses.
const Host = "https://www.furaffinity.net"
// Submission returns the canonical URL for viewing a submission.
func Submission(id int64) string {
return Host + "/view/" + strconv.FormatInt(id, 10) + "/"
}
// User returns the URL for a user's profile page.
func User(name string) string {
return Host + "/user/" + safeName(name) + "/"
}
// Gallery returns the URL for a user's main gallery page.
func Gallery(name string, page int) string {
return Host + "/gallery/" + safeName(name) + "/" + pageSegment(page)
}
// Scraps returns the URL for a user's scraps page.
func Scraps(name string, page int) string {
return Host + "/scraps/" + safeName(name) + "/" + pageSegment(page)
}
// Favorites returns the URL for a user's favorites page. FA uses a numeric
// page parameter; the first page is 1.
func Favorites(name string, page int) string {
return Host + "/favorites/" + safeName(name) + "/" + pageSegment(page)
}
// Journal returns the URL for a single journal entry.
func Journal(id int64) string {
return Host + "/journal/" + strconv.FormatInt(id, 10) + "/"
}
// UserJournals returns the URL for a user's journals listing.
func UserJournals(name string, page int) string {
return Host + "/journals/" + safeName(name) + "/" + pageSegment(page)
}
// MsgSubmissions returns the URL for the new-submission inbox. Requires auth.
func MsgSubmissions() string {
return Host + "/msg/submissions/"
}
// InboxPageSize is FA's fixed page size for the submission inbox its
// pagination links and "Next N" label are always built around 72 items.
const InboxPageSize = 72
// MsgSubmissionsCursor returns the URL for the new-submission inbox page
// that begins just below submission id FA's "new~{id}@72" cursor scheme.
// Used to keep crawling when FA omits the rendered "Next 72" link.
func MsgSubmissionsCursor(id int64) string {
return Host + "/msg/submissions/new~" +
strconv.FormatInt(id, 10) + "@" +
strconv.Itoa(InboxPageSize) + "/"
}
// MsgOthers returns the URL for the watch/journal/comment/fav notifications
// page. Requires auth.
func MsgOthers() string {
return Host + "/msg/others/"
}
// MsgPMs returns the URL for the private-message inbox. Requires auth.
func MsgPMs() string {
return Host + "/msg/pms/"
}
// ViewMessage returns the URL for a single private message (note) by ID.
// Requires auth.
func ViewMessage(id int64) string {
return Host + "/viewmessage/" + strconv.FormatInt(id, 10) + "/"
}
// Search returns the URL for a keyword search. FA accepts the query string
// directly; pagination is a query param rather than a path segment.
func Search(query string, page int) string {
u := Host + "/search/"
q := url.Values{}
q.Set("q", query)
if page > 1 {
q.Set("page", strconv.Itoa(page))
}
if e := q.Encode(); e != "" {
u += "?" + e
}
return u
}
// Browse returns the URL for /browse/ with optional page index. FA's
// browse UI navigates via POST forms, but a GET with ?page=N is honoured
// for the rendered results page, which is all this SDK needs.
func Browse(page int) string {
if page <= 1 {
return Host + "/browse/"
}
return Host + "/browse/?page=" + strconv.Itoa(page)
}
// safeName lower-cases and URL-escapes a username segment. FA folds names
// to lowercase for URL routing.
func safeName(name string) string {
return url.PathEscape(strings.ToLower(strings.TrimSpace(name)))
}
// pageSegment renders a 1-based page index as a trailing path segment.
// Returns the empty string for page <= 1 so the first page URL matches the
// canonical form FA emits in its own "next page" links.
func pageSegment(page int) string {
if page <= 1 {
return ""
}
return strconv.Itoa(page) + "/"
}
// AbsoluteCDN turns an //d.furaffinity.net/... or /art/... reference into a
// fully qualified https URL. Returns s unchanged if it already has a scheme.
func AbsoluteCDN(s string) string {
s = strings.TrimSpace(s)
switch {
case s == "":
return ""
case strings.HasPrefix(s, "http://"), strings.HasPrefix(s, "https://"):
return s
case strings.HasPrefix(s, "//"):
return "https:" + s
case strings.HasPrefix(s, "/"):
return Host + s
default:
return fmt.Sprintf("%s/%s", Host, s)
}
}