2024-06-09 13:04:22 +02:00
|
|
|
package feed
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
|
|
|
"errors"
|
|
|
|
"fmt"
|
|
|
|
"io"
|
|
|
|
"net/url"
|
|
|
|
"strings"
|
|
|
|
"text/template"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"gitea.hbanafa.com/hesham/yttopodcast/templates"
|
|
|
|
"github.com/mmcdole/gofeed"
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
const (
|
|
|
|
YT_FEED_URL = "https://www.youtube.com/feeds/videos.xml?channel_id=%s"
|
|
|
|
YT_VIDEO_URL = "https://youtube.com/watch?v=%s"
|
|
|
|
__GENERATOR_NAME = "yttopodcast - H.B."
|
|
|
|
)
|
|
|
|
|
|
|
|
type RSSMetadata struct {
|
|
|
|
Summary string
|
|
|
|
Languge string
|
|
|
|
Copyright string
|
|
|
|
BounceURL string
|
|
|
|
}
|
|
|
|
|
|
|
|
/* bounce_url in the format of http://domain/?id=%s */
|
|
|
|
func ConvertYtToRss(w io.Writer, channel_id string, bounce_url string, meta RSSMetadata) error {
|
|
|
|
|
|
|
|
var podFeed templates.FeedData
|
|
|
|
channelUrl := fmt.Sprintf(YT_FEED_URL, channel_id)
|
|
|
|
feed, err := getFeed(channelUrl)
|
|
|
|
if err != nil {
|
2024-06-09 14:05:12 +02:00
|
|
|
return feedErr(err)
|
2024-06-09 13:04:22 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
t_now := time.Now().UTC()
|
|
|
|
podFeed.Title = feed.Title
|
|
|
|
podFeed.Summary = meta.Summary
|
|
|
|
podFeed.BuildDateRfcEmail = t_now.Format(time.RFC1123Z)
|
|
|
|
podFeed.CopyRight = meta.Copyright
|
|
|
|
podFeed.PublishDateRfcEmail = t_now.Format(time.RFC1123Z)
|
|
|
|
podFeed.PodcastPage = feed.Link
|
|
|
|
podFeed.Lang = meta.Languge
|
|
|
|
podFeed.GeneratorName = __GENERATOR_NAME
|
|
|
|
|
|
|
|
for i, item := range feed.Items {
|
|
|
|
|
|
|
|
subStrings := strings.Split(item.GUID, ":")
|
|
|
|
id := subStrings[2]
|
|
|
|
|
|
|
|
bounceURL, err := url.Parse(
|
|
|
|
fmt.Sprintf(bounce_url, id),
|
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check this out
|
|
|
|
g := item.Extensions["media"]["group"]
|
|
|
|
gg := g[len(g)-1]
|
|
|
|
thumb := gg.Children["thumbnail"][0]
|
|
|
|
|
|
|
|
coverArtUrl, err := url.Parse(thumb.Attrs["url"])
|
|
|
|
if err != nil {
|
|
|
|
return errors.Join(err, errors.New(
|
|
|
|
fmt.Sprintf(
|
|
|
|
"could not parse item cover art for %s GUID: %s\n",
|
|
|
|
item.Title,
|
|
|
|
item.GUID,
|
|
|
|
)))
|
|
|
|
}
|
|
|
|
|
|
|
|
if i == 0 {
|
|
|
|
podFeed.PodcastImageURL = coverArtUrl.String()
|
|
|
|
}
|
|
|
|
|
|
|
|
podFeed.Items = append(podFeed.Items,
|
|
|
|
templates.FeedItem{
|
|
|
|
Title: item.Title,
|
|
|
|
CoverImageURL: coverArtUrl.String(),
|
|
|
|
Id: id,
|
|
|
|
Duration: "0",
|
|
|
|
PublishDateRfcEmail: item.PublishedParsed.Format(time.RFC1123Z),
|
|
|
|
Description: "TBI",
|
|
|
|
Length: 0,
|
|
|
|
EnclosureURL: bounceURL.String(),
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
rssTemplate, err := template.New("rss").Parse(templates.RSSTemplate)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
err = rssTemplate.Execute(w, podFeed)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
rssResult := bytes.Buffer{}
|
|
|
|
rssTemplate.Execute(&rssResult, podFeed)
|
|
|
|
_, err = gofeed.NewParser().ParseString(rssResult.String())
|
|
|
|
if err != nil {
|
2024-06-09 14:05:12 +02:00
|
|
|
return feedErr(err)
|
2024-06-09 13:04:22 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2024-06-09 14:05:12 +02:00
|
|
|
// Convert to Yt Atom to RSS given a Reader that provides xml
|
2024-06-09 13:04:22 +02:00
|
|
|
func convertAtomToRSS(w io.Writer, r io.Reader, meta RSSMetadata) error {
|
|
|
|
var podFeed templates.FeedData
|
|
|
|
|
|
|
|
feed, err := gofeed.NewParser().Parse(r)
|
|
|
|
if err != nil {
|
2024-06-09 14:05:12 +02:00
|
|
|
return feedErr(err)
|
2024-06-09 13:04:22 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
t_now := time.Now().UTC()
|
|
|
|
podFeed.Title = feed.Title
|
|
|
|
podFeed.Summary = meta.Summary
|
|
|
|
podFeed.BuildDateRfcEmail = t_now.Format(time.RFC1123Z)
|
|
|
|
podFeed.CopyRight = meta.Copyright
|
|
|
|
podFeed.PublishDateRfcEmail = t_now.Format(time.RFC1123Z)
|
|
|
|
podFeed.PodcastPage = feed.Link
|
|
|
|
podFeed.Lang = meta.Languge
|
|
|
|
podFeed.GeneratorName = __GENERATOR_NAME
|
|
|
|
|
|
|
|
for i, item := range feed.Items {
|
|
|
|
|
|
|
|
subStrings := strings.Split(item.GUID, ":")
|
|
|
|
id := subStrings[2]
|
|
|
|
|
|
|
|
bounceURL, err := url.Parse(
|
|
|
|
fmt.Sprintf(meta.BounceURL, id),
|
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check this out
|
|
|
|
g := item.Extensions["media"]["group"]
|
|
|
|
gg := g[len(g)-1]
|
|
|
|
thumb := gg.Children["thumbnail"][0]
|
|
|
|
|
|
|
|
coverArtUrl, err := url.Parse(thumb.Attrs["url"])
|
|
|
|
if err != nil {
|
|
|
|
return errors.Join(err, errors.New(
|
|
|
|
fmt.Sprintf(
|
|
|
|
"could not parse item cover art for %s GUID: %s\n",
|
|
|
|
item.Title,
|
|
|
|
item.GUID,
|
|
|
|
)))
|
|
|
|
}
|
|
|
|
|
|
|
|
if i == 0 {
|
|
|
|
podFeed.PodcastImageURL = coverArtUrl.String()
|
|
|
|
}
|
|
|
|
|
|
|
|
podFeed.Items = append(podFeed.Items,
|
|
|
|
templates.FeedItem{
|
|
|
|
Title: item.Title,
|
|
|
|
CoverImageURL: coverArtUrl.String(),
|
|
|
|
Id: id,
|
|
|
|
Duration: "0",
|
|
|
|
PublishDateRfcEmail: item.PublishedParsed.Format(time.RFC1123Z),
|
|
|
|
Description: "TBI",
|
|
|
|
Length: 0,
|
|
|
|
EnclosureURL: bounceURL.String(),
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
rssTemplate, err := template.New("rss").Parse(templates.RSSTemplate)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
err = rssTemplate.Execute(w, podFeed)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
rssResult := bytes.Buffer{}
|
|
|
|
rssTemplate.Execute(&rssResult, podFeed)
|
|
|
|
_, err = gofeed.NewParser().ParseString(rssResult.String())
|
|
|
|
if err != nil {
|
2024-06-09 14:05:12 +02:00
|
|
|
return feedErr(err)
|
2024-06-09 13:04:22 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2024-06-09 14:05:12 +02:00
|
|
|
func feedErr(err error) error {
|
2024-06-09 13:04:22 +02:00
|
|
|
httpErr, ok := err.(gofeed.HTTPError)
|
|
|
|
if ok {
|
|
|
|
switch httpErr.StatusCode {
|
|
|
|
case 404:
|
|
|
|
return errors.Join(err, errors.New("yt: could not find channel id"))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
func getFeed(url string) (*gofeed.Feed, error) {
|
|
|
|
parser := gofeed.NewParser()
|
|
|
|
return parser.ParseURL(url)
|
|
|
|
}
|
|
|
|
|
|
|
|
|