all repos

rss-tools @ 63e22ef

get rss feed from sources that(i need and) dont provide one

rss-tools/sources/moviefeed/api.go (view raw)

Oleksandr Smirnov Oleksandr Smirnov
olexsmir@gmail.com
add moviefeed source, 1 month ago
1
package moviefeed
2
3
import (
4
	"encoding/json"
5
	"fmt"
6
	"io"
7
	"net/http"
8
	"net/url"
9
	"strings"
10
	"time"
11
)
12
13
const (
14
	dateFormat       = "2006-01-02"
15
	tmdbBaseURL      = "https://api.themoviedb.org/3"
16
	tmdbImageBaseURL = "https://image.tmdb.org/t/p/w500"
17
)
18
19
type tmdbShow struct {
20
	ID           int    `json:"id"`
21
	Name         string `json:"name"`
22
	Overview     string `json:"overview"`
23
	FirstAirDate string `json:"first_air_date"`
24
}
25
26
type tmdbShowDetails struct {
27
	tmdbShow
28
	NumberOfSeasons int `json:"number_of_seasons"`
29
}
30
31
type TMDBEpisode struct {
32
	ID            int    `json:"id"`
33
	Name          string `json:"name"`
34
	Overview      string `json:"overview"`
35
	AirDate       string `json:"air_date"`
36
	EpisodeNumber int    `json:"episode_number"`
37
	SeasonNumber  int    `json:"season_number"`
38
	StillPath     string `json:"still_path"`
39
	ShowName      string
40
	ShowID        string
41
}
42
43
type tmdbFindResponse struct {
44
	TvResults []tmdbShow `json:"tv_results"`
45
}
46
47
type tmdbSeasonResponse struct {
48
	Episodes []TMDBEpisode `json:"episodes"`
49
}
50
51
type TMDBAPI struct {
52
	apiKey string
53
	client *http.Client
54
}
55
56
func NewTMDBAPI(apiKey string, client *http.Client) *TMDBAPI {
57
	return &TMDBAPI{
58
		apiKey: apiKey,
59
		client: client,
60
	}
61
}
62
63
func (a *TMDBAPI) FetchEpisodesForShow(showID string) ([]TMDBEpisode, error) {
64
	tmdbID, err := a.getTMDBID(showID)
65
	if err != nil {
66
		return nil, err
67
	}
68
69
	show, err := makeRequest[tmdbShowDetails](a, "/tv/%s", tmdbID)
70
	if err != nil {
71
		return nil, err
72
	}
73
74
	if show.NumberOfSeasons == 0 {
75
		return []TMDBEpisode{}, nil
76
	}
77
78
	seasonData, err := makeRequest[tmdbSeasonResponse](a, "/tv/%s/season/%d", tmdbID, show.NumberOfSeasons)
79
	if err != nil {
80
		return nil, err
81
	}
82
83
	var allEpisodes []TMDBEpisode
84
	for _, ep := range seasonData.Episodes {
85
		ep.ShowName = show.Name
86
		ep.ShowID = tmdbID
87
		allEpisodes = append(allEpisodes, ep)
88
	}
89
90
	return filterRecentEpisodes(allEpisodes), nil
91
}
92
93
func (a *TMDBAPI) getTMDBID(showID string) (string, error) {
94
	if strings.HasPrefix(showID, "tt") {
95
		result, err := makeRequest[tmdbFindResponse](a, "/find/%s?external_source=imdb_id", showID)
96
		if err != nil {
97
			return "", err
98
		}
99
100
		if len(result.TvResults) == 0 {
101
			return "", fmt.Errorf("no TMDB show found for IMDB ID %s", showID)
102
		}
103
104
		return fmt.Sprintf("%d", result.TvResults[0].ID), nil
105
	}
106
	return showID, nil
107
}
108
109
func makeRequest[T any](a *TMDBAPI, endpoint string, args ...interface{}) (*T, error) {
110
	u, err := url.Parse(fmt.Sprintf(tmdbBaseURL+endpoint, args...))
111
	if err != nil {
112
		return nil, fmt.Errorf("failed to parse URL: %w", err)
113
	}
114
	q := u.Query()
115
	q.Set("api_key", a.apiKey)
116
	u.RawQuery = q.Encode()
117
118
	resp, err := a.client.Get(u.String())
119
	if err != nil {
120
		return nil, fmt.Errorf("failed to fetch %s: %w", endpoint, err)
121
	}
122
	defer resp.Body.Close()
123
124
	if resp.StatusCode != http.StatusOK {
125
		body, _ := io.ReadAll(resp.Body)
126
		return nil, fmt.Errorf("TMDB API error: %s (status: %d)", string(body), resp.StatusCode)
127
	}
128
129
	var result T
130
	if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
131
		return nil, fmt.Errorf("failed to decode response: %w", err)
132
	}
133
134
	return &result, nil
135
}
136
137
func filterRecentEpisodes(episodes []TMDBEpisode) []TMDBEpisode {
138
	var recent []TMDBEpisode
139
	now := time.Now()
140
	cutoff := now.AddDate(0, 0, -30)
141
142
	for _, ep := range episodes {
143
		if ep.AirDate == "" {
144
			continue
145
		}
146
147
		airDate, err := time.Parse(dateFormat, ep.AirDate)
148
		if err != nil {
149
			continue
150
		}
151
152
		if airDate.Before(now) && airDate.After(cutoff) {
153
			recent = append(recent, ep)
154
		}
155
	}
156
	return recent
157
}