inital commit
This commit is contained in:
98
submission.go
Normal file
98
submission.go
Normal file
@@ -0,0 +1,98 @@
|
||||
package fa
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/PuerkitoBio/goquery"
|
||||
|
||||
"git.anthrove.art/public/go-fa-api/internal/urls"
|
||||
)
|
||||
|
||||
// Submission is a fully resolved FA submission as seen on /view/{id}/.
|
||||
type Submission struct {
|
||||
ID SubmissionID
|
||||
Title string
|
||||
Author UserRef
|
||||
PostedAt time.Time
|
||||
Rating Rating
|
||||
Category Category
|
||||
Type Type
|
||||
Species Species
|
||||
Gender Gender
|
||||
Description string // raw HTML; sanitise before rendering to a browser
|
||||
DescriptionText string // plaintext convenience
|
||||
Tags []string
|
||||
FileURL string // absolute CDN URL; pass to Download
|
||||
ThumbURL string
|
||||
Width int // 0 if unknown / non-image
|
||||
Height int
|
||||
Stats SubmissionStats
|
||||
Folders []FolderRef
|
||||
Prev SubmissionID // 0 if this is the oldest in the gallery
|
||||
Next SubmissionID // 0 if this is the newest
|
||||
|
||||
// Favorited reports whether the authenticated viewer has favorited this
|
||||
// submission. It is true only when the page was fetched with valid
|
||||
// cookies and FA rendered the "−Fav" (/unfav/) link. An anonymous fetch
|
||||
// always yields false.
|
||||
Favorited bool
|
||||
}
|
||||
|
||||
// GetSubmission fetches the submission with the given numeric ID.
|
||||
// Returns [ErrNotFound] if FA renders a "submission not found" system message,
|
||||
// [ErrUnauthorized] for restricted-visibility submissions when called
|
||||
// without valid cookies, or a wrapped parse error if the markup has shifted.
|
||||
func (c *Client) GetSubmission(ctx context.Context, id SubmissionID) (*Submission, error) {
|
||||
if id <= 0 {
|
||||
return nil, fmt.Errorf("fa: GetSubmission: id must be > 0")
|
||||
}
|
||||
var out *Submission
|
||||
err := c.fetch(ctx, urls.Submission(int64(id)), func(doc *goquery.Document) error {
|
||||
s, err := parseSubmission(id, doc)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
out = s
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// Download streams the submission's main file from the CDN into w. The same
|
||||
// rate limiter that paces /view/ fetches paces CDN fetches, so an in-flight
|
||||
// gallery iteration will yield correctly when Download is interleaved.
|
||||
//
|
||||
// Returns the number of bytes written. Errors from the writer are wrapped
|
||||
// as-is; HTTP errors come back as [*HTTPError].
|
||||
func (c *Client) Download(ctx context.Context, sub *Submission, w io.Writer) (int64, error) {
|
||||
if sub == nil {
|
||||
return 0, errors.New("fa: Download: nil submission")
|
||||
}
|
||||
if sub.FileURL == "" {
|
||||
return 0, errors.New("fa: Download: submission has no FileURL")
|
||||
}
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, sub.FileURL, nil)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
// CDN fetches share the same rate-limited transport as page fetches —
|
||||
// see RoundTrip in transport.go where the limiter gates every request.
|
||||
resp, err := c.http.Do(req)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
_, _ = io.Copy(io.Discard, resp.Body)
|
||||
return 0, &HTTPError{StatusCode: resp.StatusCode, URL: sub.FileURL}
|
||||
}
|
||||
return io.Copy(w, resp.Body)
|
||||
}
|
||||
Reference in New Issue
Block a user