package providers import ( "context" "encoding/json" "fmt" "net/http" "net/url" "strconv" "strings" "time" ) // ThePornDB matches adult scenes. It requires a free API key, so the provider // is only registered when one is configured. type ThePornDB struct { APIKey string BaseURL string Client *http.Client } // NewThePornDB builds the provider. An empty key yields a provider that finds // nothing, so a misconfiguration degrades rather than erroring. func NewThePornDB(apiKey string) *ThePornDB { return &ThePornDB{ APIKey: apiKey, BaseURL: "https://api.theporndb.net", Client: &http.Client{Timeout: 15 * time.Second}, } } func (p *ThePornDB) Name() string { return "theporndb" } func (p *ThePornDB) Kinds() []string { return []string{"adult"} } // tpdbResponse is the subset of a scene search payload we read. type tpdbResponse struct { Data []struct { ID string `json:"id"` Title string `json:"title"` Date string `json:"date"` Description string `json:"description"` Image string `json:"image"` } `json:"data"` } func (p *ThePornDB) Search(ctx context.Context, q Query) ([]Candidate, error) { title := strings.TrimSpace(q.Title) if p.APIKey == "" || title == "" { return nil, nil } params := url.Values{} params.Set("parse", title) params.Set("per_page", "5") if q.Year > 0 { params.Set("year", strconv.Itoa(q.Year)) } req, err := http.NewRequestWithContext(ctx, http.MethodGet, strings.TrimRight(p.BaseURL, "/")+"/scenes?"+params.Encode(), nil) if err != nil { return nil, err } req.Header.Set("Authorization", "Bearer "+p.APIKey) req.Header.Set("Accept", "application/json") resp, err := p.Client.Do(req) if err != nil { return nil, fmt.Errorf("theporndb: %w", err) } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { return nil, fmt.Errorf("theporndb: status %d", resp.StatusCode) } var parsed tpdbResponse if err := json.NewDecoder(resp.Body).Decode(&parsed); err != nil { return nil, fmt.Errorf("theporndb: decode: %w", err) } out := make([]Candidate, 0, len(parsed.Data)) for _, s := range parsed.Data { if s.Title == "" { continue } out = append(out, Candidate{ Provider: p.Name(), // Namespaced so a scene id cannot collide with another provider's. ExternalID: "scene/" + s.ID, Title: s.Title, Year: parseYear(s.Date), Description: stripHTML(s.Description), CoverURL: s.Image, }) } return out, nil }