all repos

rss-tools @ 19e8c1c7e354df8337b753281d599f9c45a33036

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
fix moviefeed, 28 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
)
13
14
type moviefeed struct {
15
	api   *TMDBAPI
16
	shows []string
17
}
18
19
func Register(a *app.App) error {
20
	if a.Config.MoviefeedAPIKey == "" {
21
		return nil
22
	}
23
24
	mf := &moviefeed{
25
		api:   NewTMDBAPI(a.Config.MoviefeedAPIKey, a.Client),
26
		shows: a.Config.MoviefeedShows,
27
	}
28
29
	a.Route("GET /movies", mf.handleMovies)
30
	a.Route("GET /movies/", mf.handleMovies)
31
32
	a.Logger.Info("moviefeed source registered")
33
	return nil
34
}
35
36
func (mf *moviefeed) handleMovies(w http.ResponseWriter, r *http.Request) {
37
	episodes, err := mf.fetchNewEpisodes()
38
	if err != nil {
39
		slog.Error("failed to fetch episodes", "err", err)
40
		http.Error(w, "Internal server error", http.StatusInternalServerError)
41
		return
42
	}
43
44
	feed := generateFeed(episodes)
45
	if err := feed.Render(w); err != nil {
46
		http.Error(w, "failed to render feed", http.StatusInternalServerError)
47
	}
48
}
49
50
func (mf *moviefeed) fetchNewEpisodes() ([]TMDBEpisode, error) {
51
	var allEpisodes []TMDBEpisode
52
	for _, showID := range mf.shows {
53
		episodes, err := mf.api.FetchEpisodesForShow(showID)
54
		if err != nil {
55
			slog.Warn("failed to fetch episodes for show", "show", showID, "err", err)
56
			continue
57
		}
58
		allEpisodes = append(allEpisodes, episodes...)
59
	}
60
	return allEpisodes, nil
61
}
62
63
func generateFeed(episodes []TMDBEpisode) *app.FeedBuilder {
64
	feed := app.NewFeed("moviefeed", "moviefeed").
65
		WithSubtitle("Latest episodes from followed shows")
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 := []app.FeedLink{
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, app.FeedLink{
79
				Rel:    "enclosure",
80
				Type:   "image/jpeg",
81
				Length: "0",
82
				Href:   tmdbImageBaseURL + ep.StillPath,
83
			})
84
		}
85
86
		feed.Add(app.FeedEntry{
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:     content,
96
			ContentType: contentType,
97
			Updated:     airDate,
98
			Links:       links,
99
		})
100
	}
101
	return feed
102
}
103
104
func episodeContent(ep TMDBEpisode) (string, string) {
105
	if ep.StillPath == "" {
106
		return ep.Overview, ""
107
	}
108
109
	imageURL := tmdbImageBaseURL + ep.StillPath
110
	parts := make([]string, 0, 4)
111
	parts = append(parts, "<body>")
112
	if text := strings.TrimSpace(ep.Overview); text != "" {
113
		parts = append(parts, "<p>"+html.EscapeString(text)+"</p>")
114
	}
115
	parts = append(parts,
116
		fmt.Sprintf(`<p><img src="%s" alt="%s"/></p>`, html.EscapeString(imageURL), html.EscapeString(ep.Name)))
117
	parts = append(parts, "</body>")
118
119
	return strings.Join(parts, ""), "xhtml"
120
}