Something went wrong. Try again.
let's backfill your watched movies on popfeed
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198package main
import ( "bytes" "context" "encoding/json" "errors" "fmt" "io" "net/http" "time")
var ErrAlreadyExists = errors.New("record already exists")
type Popfeeder interface { Review(ctx context.Context, m Movie, rating int) error MarkExperienced(ctx context.Context, m Movie) error}
type popfeedClient struct { token string baseURL string http httpDoer}
func newPopfeedClient(token string) *popfeedClient { return &popfeedClient{ token: token, baseURL: "https://popsphere-api.onrender.com", http: &http.Client{Timeout: 30 * time.Second}, }}
type reviewRequest struct { CreativeWorkType string `json:"creativeWorkType"` Identifiers map[string]string `json:"identifiers"` Rating *int `json:"rating,omitempty"` ReviewText string `json:"reviewText"` CreatedAt string `json:"createdAt"` Facets []string `json:"facets"` IsRevisit bool `json:"isRevisit"` ContainsSpoilers bool `json:"containsSpoilers"` Tags []string `json:"tags"` Title string `json:"title"` PosterURL string `json:"posterUrl,omitempty"` BackdropURL string `json:"backdropUrl,omitempty"` ReleaseDate string `json:"releaseDate,omitempty"` Genres []string `json:"genres,omitempty"` MainCredit string `json:"mainCredit,omitempty"` MainCreditRole string `json:"mainCreditRole,omitempty"` CrosspostTo []string `json:"crosspostTo"` MarkAsExperienced bool `json:"markAsExperienced"`}
func movieIdentifiers(m Movie) map[string]string { ids := map[string]string{"tmdbId": fmt.Sprintf("%d", m.ID)} if m.ImdbID != "" { ids["imdbId"] = m.ImdbID } return ids}
func moviePosterURL(m Movie) string { if m.PosterPath == "" { return "" } return "https://image.tmdb.org/t/p/w500" + m.PosterPath}
func movieBackdropURL(m Movie) string { if m.BackdropPath == "" { return "" } return "https://image.tmdb.org/t/p/original" + m.BackdropPath}
func newReviewRequest(m Movie, rating int, createdAt time.Time) reviewRequest { var ratingPtr *int if rating > 0 { ratingPtr = &rating } return reviewRequest{ CreativeWorkType: "movie", Identifiers: movieIdentifiers(m), Rating: ratingPtr, ReviewText: "", CreatedAt: createdAt.UTC().Format("2006-01-02T15:04:05.000Z"), Facets: []string{}, IsRevisit: false, ContainsSpoilers: false, Tags: []string{}, Title: m.Title, PosterURL: moviePosterURL(m), BackdropURL: movieBackdropURL(m), ReleaseDate: formatReleaseDate(m.ReleaseDate), Genres: m.Genres, MainCredit: m.Director, MainCreditRole: "Directed by", CrosspostTo: []string{}, MarkAsExperienced: true, }}
func newExperienceRequest(m Movie) experienceRequest { return experienceRequest{ Status: "experienced", Identifiers: movieIdentifiers(m), CreativeWorkType: "movie", Title: m.Title, PosterURL: moviePosterURL(m), BackdropURL: movieBackdropURL(m), }}
func (p *popfeedClient) Review(ctx context.Context, m Movie, rating int) error { now := time.Now() body := newReviewRequest(m, rating, now)
data, err := json.Marshal(body) if err != nil { return fmt.Errorf("encode request: %w", err) }
req, err := http.NewRequestWithContext(ctx, "POST", p.baseURL+"/reviews", bytes.NewReader(data)) if err != nil { return fmt.Errorf("create request: %w", err) } req.Header.Set("Authorization", "Bearer "+p.token) req.Header.Set("Content-Type", "application/json")
resp, err := p.http.Do(req) if err != nil { return fmt.Errorf("post review: %w", err) } defer resp.Body.Close()
switch resp.StatusCode { case http.StatusOK, http.StatusCreated: return nil case http.StatusConflict: return ErrAlreadyExists default: respBody, _ := io.ReadAll(resp.Body) return fmt.Errorf("unexpected status %d: %s", resp.StatusCode, bytes.TrimSpace(respBody)) }}
type experienceRequest struct { Status string `json:"status"` Identifiers map[string]string `json:"identifiers"` CreativeWorkType string `json:"creativeWorkType"` Title string `json:"title"` PosterURL string `json:"posterUrl,omitempty"` BackdropURL string `json:"backdropUrl,omitempty"`}
func (p *popfeedClient) MarkExperienced(ctx context.Context, m Movie) error { body := newExperienceRequest(m)
data, err := json.Marshal(body) if err != nil { return fmt.Errorf("encode request: %w", err) }
req, err := http.NewRequestWithContext(ctx, "POST", p.baseURL+"/lists/change-status", bytes.NewReader(data)) if err != nil { return fmt.Errorf("create request: %w", err) } req.Header.Set("Authorization", "Bearer "+p.token) req.Header.Set("Content-Type", "application/json")
resp, err := p.http.Do(req) if err != nil { return fmt.Errorf("mark experienced: %w", err) } defer resp.Body.Close()
switch resp.StatusCode { case http.StatusOK, http.StatusCreated: return nil default: respBody, _ := io.ReadAll(resp.Body) return fmt.Errorf("unexpected status %d: %s", resp.StatusCode, bytes.TrimSpace(respBody)) }}
func formatReleaseDate(date string) string { if date == "" { return "" } t, err := time.Parse("2006-01-02", date) if err != nil { return date } return t.Format("2006-01-02T15:04:05.000Z")}