all repos

rss-tools @ aeb1a59

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

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

Oleksandr Smirnov Oleksandr Smirnov
olexsmir@gmail.com
moviefeed: dont make any requests to the actual api, 14 days ago
1
package moviefeed
2
3
import (
4
	"fmt"
5
	"html"
6
	"log/slog"
7
	"net/http"
8
	"strings"
9
	"time"
10
11
	"olexsmir.xyz/rss-tools/app"
12
	"olexsmir.xyz/rss-tools/app/atom"
13
)
14
15
type moviefeed struct {
16
	api   episodeFetcher
17
	shows []string
18
}
19
20
type episodeFetcher interface {
21
	FetchEpisodesForShow(showID string) ([]TMDBEpisode, error)
22
}
23
24
func Register(a *app.App) error {
25
	if a.Config.MoviefeedAPIKey == "" {
26
		return nil
27
	}
28
29
	mf := &moviefeed{
30
		api:   NewTMDBAPI(a.Config.MoviefeedAPIKey, a.Client),
31
		shows: a.Config.MoviefeedShows,
32
	}
33
34
	a.Route("GET /movies", mf.handleMovies)
35
	a.Route("GET /movies/", mf.handleMovies)
36
37
	a.Logger.Info("moviefeed source registered")
38
	return nil
39
}
40
41
func (mf *moviefeed) handleMovies(w http.ResponseWriter, r *http.Request) {
42
	episodes, err := mf.fetchNewEpisodes()
43
	if err != nil {
44
		slog.Error("failed to fetch episodes", "err", err)
45
		http.Error(w, "Internal server error", http.StatusInternalServerError)
46
		return
47
	}
48
49
	feed := generateFeed(episodes)
50
	if err := feed.Render(w); err != nil {
51
		http.Error(w, "failed to render feed", http.StatusInternalServerError)
52
	}
53
}
54
55
func (mf *moviefeed) fetchNewEpisodes() ([]TMDBEpisode, error) {
56
	var allEpisodes []TMDBEpisode
57
	for _, showID := range mf.shows {
58
		episodes, err := mf.api.FetchEpisodesForShow(showID)
59
		if err != nil {
60
			slog.Warn("failed to fetch episodes for show", "show", showID, "err", err)
61
			continue
62
		}
63
		allEpisodes = append(allEpisodes, episodes...)
64
	}
65
	return allEpisodes, nil
66
}
67
68
func generateFeed(episodes []TMDBEpisode) *atom.Feed {
69
	feed := atom.NewFeed("moviefeed", "moviefeed")
70
	for i := len(episodes) - 1; i >= 0; i-- {
71
		ep := episodes[i]
72
		airDate, _ := time.Parse(dateFormat, ep.AirDate)
73
		content, contentType := episodeContent(ep)
74
		links := []atom.Link{
75
			{
76
				Rel:  "alternate",
77
				Href: fmt.Sprintf("https://www.themoviedb.org/tv/episode/%d", ep.ID),
78
			},
79
		}
80
		if ep.StillPath != "" {
81
			links = append(links, atom.Link{
82
				Rel:    "enclosure",
83
				Type:   "image/jpeg",
84
				Length: 0,
85
				Href:   tmdbImageBaseURL + ep.StillPath,
86
			})
87
		}
88
89
		feed.Add(&atom.Entry{
90
			ID: fmt.Sprintf("%s-%d-%d", ep.ShowID, ep.SeasonNumber, ep.EpisodeNumber),
91
			Title: fmt.Sprintf(
92
				"%s S%dE%d: %s",
93
				ep.ShowName,
94
				ep.SeasonNumber,
95
				ep.EpisodeNumber,
96
				ep.Name,
97
			),
98
			Content: atom.NewText(content, contentType),
99
			Updated: atom.Time(airDate),
100
			Link:    links,
101
		})
102
	}
103
	return feed
104
}
105
106
func episodeContent(ep TMDBEpisode) (string, string) {
107
	if ep.StillPath == "" {
108
		return ep.Overview, ""
109
	}
110
111
	imageURL := tmdbImageBaseURL + ep.StillPath
112
	parts := make([]string, 0, 4)
113
	parts = append(parts, "<body>")
114
	if text := strings.TrimSpace(ep.Overview); text != "" {
115
		parts = append(parts, "<p>"+html.EscapeString(text)+"</p>")
116
	}
117
	parts = append(parts,
118
		fmt.Sprintf(`<p><img src="%s" alt="%s"/></p>`, html.EscapeString(imageURL), html.EscapeString(ep.Name)))
119
	parts = append(parts, "</body>")
120
121
	return strings.Join(parts, ""), "xhtml"
122
}