all repos

rss-tools @ 71f9578bfe2969b6b22984c4264c8bf6c067e608

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