package handler import ( "encoding/xml" "net/http" "time" "podstalk/store" ) type RSSHandler struct { Store *store.Store Link string } type rssFeed struct { XMLName xml.Name `xml:"rss"` Version string `xml:"version,attr"` ItunesNS string `xml:"xmlns:itunes,attr"` Channel rssChannel `xml:"channel"` } type rssChannel struct { Title string `xml:"title"` Link string `xml:"link"` Description string `xml:"description"` Language string `xml:"language"` Author string `xml:"itunes:author,omitempty"` Image *rssImage `xml:"image,omitempty"` ItunesImage *rssItunesImage `xml:"itunes:image,omitempty"` Items []rssItem `xml:"item"` } type rssImage struct { URL string `xml:"url"` Title string `xml:"title"` Link string `xml:"link"` } type rssItunesImage struct { Href string `xml:"href,attr"` } type rssItem struct { Title string `xml:"title"` Description string `xml:"description"` Enclosure rssEnclosure `xml:"enclosure"` PubDate string `xml:"pubDate"` GUID string `xml:"guid"` ItunesImage *rssItunesImage `xml:"itunes:image,omitempty"` } type rssEnclosure struct { URL string `xml:"url,attr"` Length string `xml:"length,attr,omitempty"` Type string `xml:"type,attr"` } func (h *RSSHandler) ServeRSS(w http.ResponseWriter, r *http.Request) { // Try path-based slug first: /rss/my-slug slug := r.PathValue("slug") if slug == "" { slug = r.URL.Query().Get("slug") } if slug == "" { slug = r.URL.Query().Get("user") // legacy } var user *store.User if slug != "" { user, _ = h.Store.UserBySlug(slug) } if user == nil { // legacy: try numeric user ID if idStr := r.URL.Query().Get("user"); idStr != "" { // already tried above, fall through } } if user == nil { http.Error(w, "podcast not found", http.StatusNotFound) return } episodes, err := h.Store.ListEpisodes(user.ID) if err != nil { http.Error(w, "failed to load episodes", http.StatusInternalServerError) return } title := user.PodcastTitle if title == "" { title = "Podstalk" } author := user.PodcastAuthor items := make([]rssItem, 0, len(episodes)) for _, ep := range episodes { var itemImage *rssItunesImage epImage := ep.ImagePath if epImage == "" { epImage = user.PodcastImage } if epImage != "" { itemImage = &rssItunesImage{Href: h.fullURL(epImage)} } pubStr := ep.PublishedAt if pt, err := time.Parse("2006-01-02 15:04:05", ep.PublishedAt); err == nil { pubStr = pt.Format(time.RFC1123Z) } items = append(items, rssItem{ Title: ep.Title, Description: ep.Description, Enclosure: rssEnclosure{ URL: h.fullURL(ep.AudioPath), Type: "audio/mpeg", }, PubDate: pubStr, GUID: h.fullURL(ep.AudioPath), ItunesImage: itemImage, }) } var chanImage *rssImage var chanItunesImage *rssItunesImage if user.PodcastImage != "" { chanImage = &rssImage{ URL: h.fullURL(user.PodcastImage), Title: title, Link: h.Link, } chanItunesImage = &rssItunesImage{Href: h.fullURL(user.PodcastImage)} } feed := rssFeed{ Version: "2.0", ItunesNS: "http://www.itunes.com/dtds/podcast-1.0.dtd", Channel: rssChannel{ Title: title, Link: h.Link, Description: title, Language: "en-us", Author: author, Image: chanImage, ItunesImage: chanItunesImage, Items: items, }, } w.Header().Set("Content-Type", "application/rss+xml; charset=utf-8") w.Write([]byte(xml.Header)) enc := xml.NewEncoder(w) enc.Indent("", " ") enc.Encode(feed) } func (h *RSSHandler) fullURL(path string) string { if path == "" { return "" } if h.Link == "" { return path } return h.Link + "/uploads/" + path }