From a33b90c5c77cc4fb29dab2a80a06319edd30be4a Mon Sep 17 00:00:00 2001 From: HeshamTB Date: Sun, 9 Jun 2024 14:04:22 +0300 Subject: [PATCH] init: tools and commands Signed-off-by: HeshamTB --- bouncer/bounce.go | 88 +++++ bouncer/middle.go | 15 + cmd/genfeed/.gitignore | 1 + cmd/genfeed/genfeed.go | 43 +++ cmd/ytbouncer/ytbouncer.go | 32 ++ feed/feed.go | 214 +++++++++++ feed/feed_test.go | 100 +++++ feed/testdata/a.xml | 697 +++++++++++++++++++++++++++++++++++ feed/testdata/b.xml | 697 +++++++++++++++++++++++++++++++++++ feed/testdata/c.xml | 491 ++++++++++++++++++++++++ feed/testdata/feedlink.txt | 1 + go.mod | 23 ++ go.sum | 87 +++++ templates/base.rss.templ | 54 +++ templates/item.rss.templ | 17 + templates/types.go | 36 ++ ytlinkprov/cache_provider.go | 80 ++++ ytlinkprov/common.go | 6 + 18 files changed, 2682 insertions(+) create mode 100644 bouncer/bounce.go create mode 100644 bouncer/middle.go create mode 100644 cmd/genfeed/.gitignore create mode 100644 cmd/genfeed/genfeed.go create mode 100644 cmd/ytbouncer/ytbouncer.go create mode 100644 feed/feed.go create mode 100644 feed/feed_test.go create mode 100644 feed/testdata/a.xml create mode 100644 feed/testdata/b.xml create mode 100644 feed/testdata/c.xml create mode 100644 feed/testdata/feedlink.txt create mode 100644 go.mod create mode 100644 go.sum create mode 100644 templates/base.rss.templ create mode 100644 templates/item.rss.templ create mode 100644 templates/types.go create mode 100644 ytlinkprov/cache_provider.go create mode 100644 ytlinkprov/common.go diff --git a/bouncer/bounce.go b/bouncer/bounce.go new file mode 100644 index 0000000..4d637ac --- /dev/null +++ b/bouncer/bounce.go @@ -0,0 +1,88 @@ +package bouncer + +import ( + "context" + "fmt" + "log" + "net/http" + "os" + "strings" + "time" + + "gitea.hbanafa.com/hesham/yttopodcast/ytlinkprov" + "github.com/lrstanley/go-ytdlp" +) + +const CTX_LINKPROV = "linkprov" + +type Bouncer struct { + http.Server + ytdlpInstall ytdlp.ResolvedInstall + urlProvider ytlinkprov.YtLinkProvider +} + +func NewBouncerHTTPServer( + ctx context.Context, + listAddr string, + link_prov ytlinkprov.YtLinkProvider, +) (srv *Bouncer, err error) { + + ytInstall, err := ytdlp.Install( + ctx, + &ytdlp.InstallOptions{ + AllowVersionMismatch: true, + }, + ) + if err != nil { + return nil, err + } + + mux := http.NewServeMux() + mux.HandleFunc("GET /{$}", handleGETBounce) + + var httpHandler http.Handler = mux + httpHandler = UrlCache(mux, link_prov) + + return &Bouncer{ + urlProvider: link_prov, + Server: http.Server{ + WriteTimeout: time.Second * 60, + ReadTimeout: time.Second * 60, + Addr: listAddr, + Handler: httpHandler, + }, + ytdlpInstall: *ytInstall, + }, nil +} + +func handleGETBounce(w http.ResponseWriter, r *http.Request) { + urlProv, ok := r.Context().Value(CTX_LINKPROV).(ytlinkprov.YtLinkProvider) + if !ok { + fmt.Fprintf(os.Stderr, "Could not get url provider from ctx!\n") + w.WriteHeader(http.StatusInternalServerError) + return + } + + id := r.URL.Query().Get("id") + if id == "" { + w.WriteHeader(http.StatusBadRequest) + return + } + log.Printf("request for %s", id) + // vidUrl := fmt.Sprintf("https://youtube.com/watch?v=%s", id) + // ytCmd := ytdlp.New().ExtractAudio().GetURL() + // ytRes, err := ytCmd.Run(r.Context(), vidUrl) + link, err := urlProv.GetLink(id) + if err != nil { + _, ok := err.(*ytdlp.ErrExitCode) + if ok { + w.WriteHeader(http.StatusBadRequest) + return + } + fmt.Fprintln(os.Stderr, err.Error()) + w.WriteHeader(http.StatusInternalServerError) + return + } + w.Header().Set("Content-Type", "audio/mp3") + http.Redirect(w, r, strings.Trim(link, "\n"), http.StatusFound) +} diff --git a/bouncer/middle.go b/bouncer/middle.go new file mode 100644 index 0000000..bf3053d --- /dev/null +++ b/bouncer/middle.go @@ -0,0 +1,15 @@ +package bouncer + +import ( + "context" + "net/http" + + "gitea.hbanafa.com/hesham/yttopodcast/ytlinkprov" +) + +func UrlCache(next http.Handler, url_prov ytlinkprov.YtLinkProvider) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + rr := r.WithContext(context.WithValue(r.Context(), CTX_LINKPROV, url_prov)) + next.ServeHTTP(w, rr) + }) +} diff --git a/cmd/genfeed/.gitignore b/cmd/genfeed/.gitignore new file mode 100644 index 0000000..44df740 --- /dev/null +++ b/cmd/genfeed/.gitignore @@ -0,0 +1 @@ +genfeed diff --git a/cmd/genfeed/genfeed.go b/cmd/genfeed/genfeed.go new file mode 100644 index 0000000..98e8d8a --- /dev/null +++ b/cmd/genfeed/genfeed.go @@ -0,0 +1,43 @@ +package main + +import ( + "errors" + "flag" + "fmt" + "os" + + "gitea.hbanafa.com/hesham/yttopodcast/feed" +) + +const ( + EXIT_ERR_BAD_CLI = 64 +) + +var ( + chan_id = flag.String("id", "", "YouTube channel ID") + bounc_url = flag.String("bouncer", "http://localhost:8081/?id=%s", "Bouncer url as format string") + lang = flag.String("lang", "en", "Content Language") +) + +func main() { + flag.Parse() + if err := validFlags(); err != nil { + fmt.Fprintln(os.Stderr, err.Error()) + os.Exit(EXIT_ERR_BAD_CLI) + } + fmt.Fprintf(os.Stderr, "id: %s\nbouncer: %s\n", *chan_id, *bounc_url) + + err := feed.ConvertYtToRss(os.Stdout, *chan_id, *bounc_url, + feed.RSSMetadata{Languge: "en", Copyright: "N/A", Summary: "YouTube Channel as podcast"}) + if err != nil { + fmt.Fprintln(os.Stderr, err.Error()) + os.Exit(1) + } +} + +func validFlags() error { + if *chan_id == "" { + return errors.New("flag: id flag missing") + } + return nil +} diff --git a/cmd/ytbouncer/ytbouncer.go b/cmd/ytbouncer/ytbouncer.go new file mode 100644 index 0000000..312c0d9 --- /dev/null +++ b/cmd/ytbouncer/ytbouncer.go @@ -0,0 +1,32 @@ +package main + +import ( + "context" + "flag" + "fmt" + "os" + "time" + + "gitea.hbanafa.com/hesham/yttopodcast/bouncer" + "gitea.hbanafa.com/hesham/yttopodcast/ytlinkprov" +) + +var listenAddr = flag.String("listen-addr", ":8081", "Address and port to listen on") + +func main() { + + ctx := context.Background() + flag.Parse() + linkProv, err := ytlinkprov.NewCachedLinkProvider(time.Minute * 30) + + bouncer, err := bouncer.NewBouncerHTTPServer(ctx, *listenAddr, linkProv) + if err != nil { + fmt.Fprintln(os.Stderr, err.Error()) + } + + fmt.Printf("Starting server on %s\n", *listenAddr) + err = bouncer.ListenAndServe() + if err != nil { + fmt.Fprintln(os.Stderr, err.Error()) + } +} diff --git a/feed/feed.go b/feed/feed.go new file mode 100644 index 0000000..1eb3c9b --- /dev/null +++ b/feed/feed.go @@ -0,0 +1,214 @@ +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_FEED_SAM = "https://www.youtube.com/feeds/videos.xml?channel_id=UC5Uxq95L6K60XVdNPmUxoYA" + 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 { + return FeedErr(err) + } + + 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 { + return FeedErr(err) + } + + return nil +} + +func convertAtomToRSS(w io.Writer, r io.Reader, meta RSSMetadata) error { + var podFeed templates.FeedData + + feed, err := gofeed.NewParser().Parse(r) + if err != nil { + return FeedErr(err) + } + + 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 { + return FeedErr(err) + } + + return nil +} + +func FeedErr(err error) error { + 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) +} + + diff --git a/feed/feed_test.go b/feed/feed_test.go new file mode 100644 index 0000000..edf2ba6 --- /dev/null +++ b/feed/feed_test.go @@ -0,0 +1,100 @@ +package feed + +import ( + "bytes" + "io" + "net/http" + "net/url" + "os" + "strings" + "testing" + + "github.com/mmcdole/gofeed" +) + +func TestParseYTAtom(t *testing.T) { + fnames := []string{"a.xml", "b.xml", "c.xml"} + for _, n := range fnames { + nn := "testdata/" + n + runTestYTAtom(t, nn) + } +} + +func runTestYTAtom(t *testing.T, fname string) { + t.Run(fname, func(t *testing.T) { + + f, err := os.Open(fname) + if err != nil { + t.Error(err.Error()) + } + + feed, err := io.ReadAll(f) + if err != nil { + t.Error(err.Error()) + } + + parser := gofeed.NewParser() + parser.ParseString(string(feed)) + + }) +} + +func TestFetchRemoteYTFeed(t *testing.T) { + f, err := os.Open("testdata/feedlink.txt") + if err != nil { + t.Log(err.Error()) + t.FailNow() + } + + linkb, err := io.ReadAll(f) + if err != nil { + t.Log(err.Error()) + t.FailNow() + } + + link := strings.Trim(string(linkb), "\n") + + _, err = url.Parse(link) + if err != nil { + t.Log(err.Error()) + t.FailNow() + } + + resp, err := http.Get(link) + if err != nil { + t.Log(err.Error()) + t.FailNow() + } + defer resp.Body.Close() + + _, err = gofeed.NewParser().Parse(resp.Body) + if err != nil { + t.Log(err.Error()) + t.FailNow() + } + +} + +func TestAtomToRSS(t *testing.T) { + f, err := os.Open("testdata/a.xml") + if err != nil { + t.Error(err.Error()) + t.FailNow() + } + + buf := bytes.Buffer{} + + err = convertAtomToRSS(&buf, f, RSSMetadata{BounceURL: "http://localhost:8081/q=%s"}) + if err != nil { + t.Error(err.Error()) + t.FailNow() + } + + _, err = gofeed.NewParser().Parse(&buf) + if err != nil { + t.Error(err.Error()) + t.FailNow() + } + +} + diff --git a/feed/testdata/a.xml b/feed/testdata/a.xml new file mode 100644 index 0000000..8bf4c70 --- /dev/null +++ b/feed/testdata/a.xml @@ -0,0 +1,697 @@ + + + + yt:channel:5Uxq95L6K60XVdNPmUxoYA + 5Uxq95L6K60XVdNPmUxoYA + Samito + + + Samito + https://www.youtube.com/channel/UC5Uxq95L6K60XVdNPmUxoYA + + 2016-07-10T17:00:53+00:00 + + yt:video:yUiFPNeCWKw + yUiFPNeCWKw + UC5Uxq95L6K60XVdNPmUxoYA + OVERWATCH 2 6V6 RESPONSE VIDEO TODAY! TOP 500 RANKED - COACHING !PATREON !AD + + + Samito + https://www.youtube.com/channel/UC5Uxq95L6K60XVdNPmUxoYA + + 2024-06-05T15:59:03+00:00 + 2024-06-05T16:00:22+00:00 + + OVERWATCH 2 6V6 RESPONSE VIDEO TODAY! TOP 500 RANKED - COACHING !PATREON !AD + + + Leave a like if you enjoyed & subscribe for more: http://bit.ly/2ttxnC0 + +Business inquiries ONLY (Private coaching is not a business inquiry!): +samito@loadscreen.gg + +Stream Schedule: +Mon-Friday @ 11 AM EST ON THIS CHANNEL! + +Follow me on Twitter: +https://www.twitter.com/SamitoFPS + + +Join my discord! +https://discord.gg/f6spsMA + + +Twitch: +https://twitch.tv/SamitoFPS + + +#Overwatch2 #LiveStream #ad + + + + + + + + yt:video:2Pi2sVwa8Sc + 2Pi2sVwa8Sc + UC5Uxq95L6K60XVdNPmUxoYA + OVERWATCH 2 NEW TIER LIST TODAY TOP 500 RANKED - COACHING !PATREON !AD + + + Samito + https://www.youtube.com/channel/UC5Uxq95L6K60XVdNPmUxoYA + + 2024-06-04T21:21:11+00:00 + 2024-06-05T07:54:54+00:00 + + OVERWATCH 2 NEW TIER LIST TODAY TOP 500 RANKED - COACHING !PATREON !AD + + + Leave a like if you enjoyed & subscribe for more: http://bit.ly/2ttxnC0 + +Business inquiries ONLY (Private coaching is not a business inquiry!): +samito@loadscreen.gg + +Stream Schedule: +Mon-Friday @ 11 AM EST ON THIS CHANNEL! + +Follow me on Twitter: +https://www.twitter.com/SamitoFPS + + +Join my discord! +https://discord.gg/f6spsMA + + +Twitch: +https://twitch.tv/SamitoFPS + + +#Overwatch2 #LiveStream #ad + + + + + + + + yt:video:80sv9fU3_wo + 80sv9fU3_wo + UC5Uxq95L6K60XVdNPmUxoYA + OVERWATCH 2 TOP 500 COACHING & RANKED - !PATREON !AD + + + Samito + https://www.youtube.com/channel/UC5Uxq95L6K60XVdNPmUxoYA + + 2024-06-03T18:09:28+00:00 + 2024-06-04T18:27:58+00:00 + + OVERWATCH 2 TOP 500 COACHING & RANKED - !PATREON !AD + + + Leave a like if you enjoyed & subscribe for more: http://bit.ly/2ttxnC0 + +Business inquiries ONLY (Private coaching is not a business inquiry!): +samito@loadscreen.gg + +Stream Schedule: +Mon-Friday @ 11 AM EST ON THIS CHANNEL! + +Follow me on Twitter: +https://www.twitter.com/SamitoFPS + + +Join my discord! +https://discord.gg/f6spsMA + + +Twitch: +https://twitch.tv/SamitoFPS + + +#Overwatch2 #LiveStream #ad + + + + + + + + yt:video:8UwTveGCrOQ + 8UwTveGCrOQ + UC5Uxq95L6K60XVdNPmUxoYA + The DEVS Have DELETED Hanzo in Overwatch 2 (I'm pissed) + + + Samito + https://www.youtube.com/channel/UC5Uxq95L6K60XVdNPmUxoYA + + 2024-06-01T16:40:13+00:00 + 2024-06-03T05:04:21+00:00 + + The DEVS Have DELETED Hanzo in Overwatch 2 (I'm pissed) + + + Check out Patreon for coaching and community!!! https://www.patreon.com/samito27/membership + +Leave a like if you enjoyed & subscribe for more: http://bit.ly/2ttxnC0 + +Business inquiries ONLY (Private coaching is not a business inquiry!): +samito@loadscreen.gg + +Stream Schedule: +Mon-Friday @ 11 AM EST ON THIS CHANNEL! + +Follow me on Twitter: +https://www.twitter.com/SamitoFPS + + +Join my discord! +https://discord.gg/f6spsMA + + +Twitch: +https://twitch.tv/SamitoFPS + +Edited by: +https://twitter.com/senzeehpai + + +#Overwatch + + + + + + + + yt:video:jiD_futRl7M + jiD_futRl7M + UC5Uxq95L6K60XVdNPmUxoYA + OVERWATCH 2 LEARNING NEW PHARAH DAY 3 OP HERO !PATREON !AD + + + Samito + https://www.youtube.com/channel/UC5Uxq95L6K60XVdNPmUxoYA + + 2024-05-31T20:14:37+00:00 + 2024-06-02T09:35:50+00:00 + + OVERWATCH 2 LEARNING NEW PHARAH DAY 3 OP HERO !PATREON !AD + + + Leave a like if you enjoyed & subscribe for more: http://bit.ly/2ttxnC0 + +Business inquiries ONLY (Private coaching is not a business inquiry!): +samito@loadscreen.gg + +Stream Schedule: +Mon-Friday @ 11 AM EST ON THIS CHANNEL! + +Follow me on Twitter: +https://www.twitter.com/SamitoFPS + + +Join my discord! +https://discord.gg/f6spsMA + + +Twitch: +https://twitch.tv/SamitoFPS + + +#Overwatch2 #LiveStream #ad + + + + + + + + yt:video:Grr3dX1NX8U + Grr3dX1NX8U + UC5Uxq95L6K60XVdNPmUxoYA + Actual Solutions to 5v5 and 6v6 in Overwatch 2 - (with Spilo) + + + Samito + https://www.youtube.com/channel/UC5Uxq95L6K60XVdNPmUxoYA + + 2024-05-30T21:16:25+00:00 + 2024-06-05T01:56:26+00:00 + + Actual Solutions to 5v5 and 6v6 in Overwatch 2 - (with Spilo) + + + Check out @StormcrowProductions + +Leave a like if you enjoyed & subscribe for more: http://bit.ly/2ttxnC0 + +Business inquiries ONLY (Private coaching is not a business inquiry!): +samito@loadscreen.gg + + +Stream Schedule: +Mon-Friday @ 11 AM EST ON THIS CHANNEL! + +Follow me on Twitter: +https://www.twitter.com/SamitoFPS + +Join my discord! +https://discord.gg/f6spsMA + + +Twitch: +https://twitch.tv/SamitoFPS + +Edited by: + +#Overwatch + + + + + + + + yt:video:NAT9jggf1Q8 + NAT9jggf1Q8 + UC5Uxq95L6K60XVdNPmUxoYA + OVERWATCH 2 LEARNING NEW PHARAH DAY 2 !PATREON !AD + + + Samito + https://www.youtube.com/channel/UC5Uxq95L6K60XVdNPmUxoYA + + 2024-05-30T19:29:02+00:00 + 2024-06-04T22:08:58+00:00 + + OVERWATCH 2 LEARNING NEW PHARAH DAY 2 !PATREON !AD + + + πŸš€ Install Star Trek Fleet Command for FREE now https://t2m.io/STFC_Samito and enter the promo code WARPSPEED to unlock 10 Epic Shards of Kirk, enhancing your command instantly! + +Leave a like if you enjoyed & subscribe for more: http://bit.ly/2ttxnC0 + +Business inquiries ONLY (Private coaching is not a business inquiry!): +samito@loadscreen.gg + +Stream Schedule: +Mon-Friday @ 11 AM EST ON THIS CHANNEL! + +Follow me on Twitter: +https://www.twitter.com/SamitoFPS + + +Join my discord! +https://discord.gg/f6spsMA + + +Twitch: +https://twitch.tv/SamitoFPS + + +#Overwatch2 #LiveStream #ad + + + + + + + + yt:video:l7X8x8Ec8tE + l7X8x8Ec8tE + UC5Uxq95L6K60XVdNPmUxoYA + OVERWATCH 2 LEARNING NEW PHARAH DAY 1 !PATREON !AD + + + Samito + https://www.youtube.com/channel/UC5Uxq95L6K60XVdNPmUxoYA + + 2024-05-29T19:19:48+00:00 + 2024-06-01T04:51:56+00:00 + + OVERWATCH 2 LEARNING NEW PHARAH DAY 1 !PATREON !AD + + + πŸš€ Install Star Trek Fleet Command for FREE now https://t2m.io/STFC_Samito and enter the promo code WARPSPEED to unlock 10 Epic Shards of Kirk, enhancing your command instantly! + +Leave a like if you enjoyed & subscribe for more: http://bit.ly/2ttxnC0 + +Business inquiries ONLY (Private coaching is not a business inquiry!): +samito@loadscreen.gg + +Stream Schedule: +Mon-Friday @ 11 AM EST ON THIS CHANNEL! + +Follow me on Twitter: +https://www.twitter.com/SamitoFPS + + +Join my discord! +https://discord.gg/f6spsMA + + +Twitch: +https://twitch.tv/SamitoFPS + + +#Overwatch2 #LiveStream #ad + + + + + + + + yt:video:ksze1pinp78 + ksze1pinp78 + UC5Uxq95L6K60XVdNPmUxoYA + PLAT Support Needs a Reality Check - COACH ROASTS STUDENT | Overwatch 2 + + + Samito + https://www.youtube.com/channel/UC5Uxq95L6K60XVdNPmUxoYA + + 2024-05-29T14:40:08+00:00 + 2024-06-01T02:19:56+00:00 + + PLAT Support Needs a Reality Check - COACH ROASTS STUDENT | Overwatch 2 + + + Check out Patreon for coaching and community!!! https://www.patreon.com/samito27/membership + +Leave a like if you enjoyed & subscribe for more: http://bit.ly/2ttxnC0 + +Business inquiries ONLY (Private coaching is not a business inquiry!): +samito@loadscreen.gg + +Stream Schedule: +Mon-Friday @ 11 AM EST ON THIS CHANNEL! + +Follow me on Twitter: +https://www.twitter.com/SamitoFPS + + +Join my discord! +https://discord.gg/f6spsMA + + +Twitch: +https://twitch.tv/SamitoFPS + +Edited by: +https://twitter.com/senzeehpai + + +#Overwatch + + + + + + + + yt:video:uqkGZpJpCq8 + uqkGZpJpCq8 + UC5Uxq95L6K60XVdNPmUxoYA + When you need to convince the homie to download a new game + + + Samito + https://www.youtube.com/channel/UC5Uxq95L6K60XVdNPmUxoYA + + 2024-05-29T13:45:04+00:00 + 2024-06-01T21:42:26+00:00 + + When you need to convince the homie to download a new game + + + Leave a like if you enjoyed & subscribe for more: http://bit.ly/2ttxnC0 + +Business inquiries ONLY (Private coaching is not a business inquiry!): +samito@loadscreen.gg + + +Stream Schedule: +Mon-Friday @ 11 AM EST ON THIS CHANNEL! + +Follow me on Twitter: +https://www.twitter.com/SamitoFPS + + +Join my discord! +https://discord.gg/f6spsMA + + +Twitch: +https://twitch.tv/SamitoFPS + +Edited by: +https://twitter.com/senzeehpai + + +#Overwatch + + + + + + + + yt:video:vmq1ZpCKoCg + vmq1ZpCKoCg + UC5Uxq95L6K60XVdNPmUxoYA + Here's Why Marvel Rivals Changes Everything + + + Samito + https://www.youtube.com/channel/UC5Uxq95L6K60XVdNPmUxoYA + + 2024-05-28T16:29:53+00:00 + 2024-06-01T21:47:38+00:00 + + Here's Why Marvel Rivals Changes Everything + + + For PRIVATE 6v6 Scrim Server Access - https://www.patreon.com/samito27/membership + +Business inquiries ONLY (Private coaching is not a business inquiry!): +samito@loadscreen.gg + +Check out! Sprixy +https://youtu.be/GQHS9bD_IvQ?si=rsIYq5QjHnC4VywJ + +Stream Schedule: +Mon-Friday @ 11 AM EST ON THIS CHANNEL! + +Follow me on Twitter: +https://www.twitter.com/SamitoFPS + +Join my discord! +https://discord.gg/f6spsMA + +Twitch: +https://twitch.tv/SamitoFPS + +Edited by: +Shadin + +#Overwatch + + + + + + + + yt:video:23Fj9HaXqEA + 23Fj9HaXqEA + UC5Uxq95L6K60XVdNPmUxoYA + Support Mains are BOOSTED + + + Samito + https://www.youtube.com/channel/UC5Uxq95L6K60XVdNPmUxoYA + + 2024-05-28T13:45:00+00:00 + 2024-06-03T01:36:39+00:00 + + Support Mains are BOOSTED + + + Leave a like if you enjoyed & subscribe for more: http://bit.ly/2ttxnC0 + +Business inquiries ONLY (Private coaching is not a business inquiry!): +samito@loadscreen.gg + + +Stream Schedule: +Mon-Friday @ 11 AM EST ON THIS CHANNEL! + +Follow me on Twitter: +https://www.twitter.com/SamitoFPS + + +Join my discord! +https://discord.gg/f6spsMA + + +Twitch: +https://twitch.tv/SamitoFPS + +Edited by: +https://twitter.com/senzeehpai + + +#Overwatch + + + + + + + + yt:video:IYGs54YQw9s + IYGs54YQw9s + UC5Uxq95L6K60XVdNPmUxoYA + Samito Might be a Little Mad + + + Samito + https://www.youtube.com/channel/UC5Uxq95L6K60XVdNPmUxoYA + + 2024-05-27T13:00:13+00:00 + 2024-05-29T11:02:38+00:00 + + Samito Might be a Little Mad + + + Leave a like if you enjoyed & subscribe for more: http://bit.ly/2ttxnC0 + +Business inquiries ONLY (Private coaching is not a business inquiry!): +samito@loadscreen.gg + + +Stream Schedule: +Mon-Friday @ 11 AM EST ON THIS CHANNEL! + +Follow me on Twitter: +https://www.twitter.com/SamitoFPS + + +Join my discord! +https://discord.gg/f6spsMA + + +Twitch: +https://twitch.tv/SamitoFPS + +Edited by: +https://twitter.com/senzeehpai + + +#Overwatch + + + + + + + + yt:video:KF6FDX8ju9U + KF6FDX8ju9U + UC5Uxq95L6K60XVdNPmUxoYA + It's Time to STOP the Overwatch Shill Accusations (kinda) + + + Samito + https://www.youtube.com/channel/UC5Uxq95L6K60XVdNPmUxoYA + + 2024-05-26T17:46:29+00:00 + 2024-05-29T15:10:12+00:00 + + It's Time to STOP the Overwatch Shill Accusations (kinda) + + + Check out @GSRaider + +Leave a like if you enjoyed & subscribe for more: http://bit.ly/2ttxnC0 + +Business inquiries ONLY (Private coaching is not a business inquiry!): +samito@loadscreen.gg + + +Stream Schedule: +Mon-Friday @ 11 AM EST ON THIS CHANNEL! + +Follow me on Twitter: +https://www.twitter.com/SamitoFPS + +Join my discord! +https://discord.gg/f6spsMA + + +Twitch: +https://twitch.tv/SamitoFPS + +Edited by: +https://twitter.com/senzeehpai + + +#Overwatch + + + + + + + + yt:video:eWpanQXLPTY + eWpanQXLPTY + UC5Uxq95L6K60XVdNPmUxoYA + OVERWATCH 2 NEW TANK CHANGES FUN ROLE!! Star Trek Fleet Command Later !AD !PATREON + + + Samito + https://www.youtube.com/channel/UC5Uxq95L6K60XVdNPmUxoYA + + 2024-05-25T21:00:39+00:00 + 2024-05-31T22:17:49+00:00 + + OVERWATCH 2 NEW TANK CHANGES FUN ROLE!! Star Trek Fleet Command Later !AD !PATREON + + + πŸš€ Install Star Trek Fleet Command for FREE now https://t2m.io/STFC_Samito and enter the promo code WARPSPEED to unlock 10 Epic Shards of Kirk, enhancing your command instantly! + +Leave a like if you enjoyed & subscribe for more: http://bit.ly/2ttxnC0 + +Business inquiries ONLY (Private coaching is not a business inquiry!): +samito@loadscreen.gg + +Stream Schedule: +Mon-Friday @ 11 AM EST ON THIS CHANNEL! + +Follow me on Twitter: +https://www.twitter.com/SamitoFPS + + +Join my discord! +https://discord.gg/f6spsMA + + +Twitch: +https://twitch.tv/SamitoFPS + + +#Overwatch2 #LiveStream #ad + + + + + + + diff --git a/feed/testdata/b.xml b/feed/testdata/b.xml new file mode 100644 index 0000000..8bf4c70 --- /dev/null +++ b/feed/testdata/b.xml @@ -0,0 +1,697 @@ + + + + yt:channel:5Uxq95L6K60XVdNPmUxoYA + 5Uxq95L6K60XVdNPmUxoYA + Samito + + + Samito + https://www.youtube.com/channel/UC5Uxq95L6K60XVdNPmUxoYA + + 2016-07-10T17:00:53+00:00 + + yt:video:yUiFPNeCWKw + yUiFPNeCWKw + UC5Uxq95L6K60XVdNPmUxoYA + OVERWATCH 2 6V6 RESPONSE VIDEO TODAY! TOP 500 RANKED - COACHING !PATREON !AD + + + Samito + https://www.youtube.com/channel/UC5Uxq95L6K60XVdNPmUxoYA + + 2024-06-05T15:59:03+00:00 + 2024-06-05T16:00:22+00:00 + + OVERWATCH 2 6V6 RESPONSE VIDEO TODAY! TOP 500 RANKED - COACHING !PATREON !AD + + + Leave a like if you enjoyed & subscribe for more: http://bit.ly/2ttxnC0 + +Business inquiries ONLY (Private coaching is not a business inquiry!): +samito@loadscreen.gg + +Stream Schedule: +Mon-Friday @ 11 AM EST ON THIS CHANNEL! + +Follow me on Twitter: +https://www.twitter.com/SamitoFPS + + +Join my discord! +https://discord.gg/f6spsMA + + +Twitch: +https://twitch.tv/SamitoFPS + + +#Overwatch2 #LiveStream #ad + + + + + + + + yt:video:2Pi2sVwa8Sc + 2Pi2sVwa8Sc + UC5Uxq95L6K60XVdNPmUxoYA + OVERWATCH 2 NEW TIER LIST TODAY TOP 500 RANKED - COACHING !PATREON !AD + + + Samito + https://www.youtube.com/channel/UC5Uxq95L6K60XVdNPmUxoYA + + 2024-06-04T21:21:11+00:00 + 2024-06-05T07:54:54+00:00 + + OVERWATCH 2 NEW TIER LIST TODAY TOP 500 RANKED - COACHING !PATREON !AD + + + Leave a like if you enjoyed & subscribe for more: http://bit.ly/2ttxnC0 + +Business inquiries ONLY (Private coaching is not a business inquiry!): +samito@loadscreen.gg + +Stream Schedule: +Mon-Friday @ 11 AM EST ON THIS CHANNEL! + +Follow me on Twitter: +https://www.twitter.com/SamitoFPS + + +Join my discord! +https://discord.gg/f6spsMA + + +Twitch: +https://twitch.tv/SamitoFPS + + +#Overwatch2 #LiveStream #ad + + + + + + + + yt:video:80sv9fU3_wo + 80sv9fU3_wo + UC5Uxq95L6K60XVdNPmUxoYA + OVERWATCH 2 TOP 500 COACHING & RANKED - !PATREON !AD + + + Samito + https://www.youtube.com/channel/UC5Uxq95L6K60XVdNPmUxoYA + + 2024-06-03T18:09:28+00:00 + 2024-06-04T18:27:58+00:00 + + OVERWATCH 2 TOP 500 COACHING & RANKED - !PATREON !AD + + + Leave a like if you enjoyed & subscribe for more: http://bit.ly/2ttxnC0 + +Business inquiries ONLY (Private coaching is not a business inquiry!): +samito@loadscreen.gg + +Stream Schedule: +Mon-Friday @ 11 AM EST ON THIS CHANNEL! + +Follow me on Twitter: +https://www.twitter.com/SamitoFPS + + +Join my discord! +https://discord.gg/f6spsMA + + +Twitch: +https://twitch.tv/SamitoFPS + + +#Overwatch2 #LiveStream #ad + + + + + + + + yt:video:8UwTveGCrOQ + 8UwTveGCrOQ + UC5Uxq95L6K60XVdNPmUxoYA + The DEVS Have DELETED Hanzo in Overwatch 2 (I'm pissed) + + + Samito + https://www.youtube.com/channel/UC5Uxq95L6K60XVdNPmUxoYA + + 2024-06-01T16:40:13+00:00 + 2024-06-03T05:04:21+00:00 + + The DEVS Have DELETED Hanzo in Overwatch 2 (I'm pissed) + + + Check out Patreon for coaching and community!!! https://www.patreon.com/samito27/membership + +Leave a like if you enjoyed & subscribe for more: http://bit.ly/2ttxnC0 + +Business inquiries ONLY (Private coaching is not a business inquiry!): +samito@loadscreen.gg + +Stream Schedule: +Mon-Friday @ 11 AM EST ON THIS CHANNEL! + +Follow me on Twitter: +https://www.twitter.com/SamitoFPS + + +Join my discord! +https://discord.gg/f6spsMA + + +Twitch: +https://twitch.tv/SamitoFPS + +Edited by: +https://twitter.com/senzeehpai + + +#Overwatch + + + + + + + + yt:video:jiD_futRl7M + jiD_futRl7M + UC5Uxq95L6K60XVdNPmUxoYA + OVERWATCH 2 LEARNING NEW PHARAH DAY 3 OP HERO !PATREON !AD + + + Samito + https://www.youtube.com/channel/UC5Uxq95L6K60XVdNPmUxoYA + + 2024-05-31T20:14:37+00:00 + 2024-06-02T09:35:50+00:00 + + OVERWATCH 2 LEARNING NEW PHARAH DAY 3 OP HERO !PATREON !AD + + + Leave a like if you enjoyed & subscribe for more: http://bit.ly/2ttxnC0 + +Business inquiries ONLY (Private coaching is not a business inquiry!): +samito@loadscreen.gg + +Stream Schedule: +Mon-Friday @ 11 AM EST ON THIS CHANNEL! + +Follow me on Twitter: +https://www.twitter.com/SamitoFPS + + +Join my discord! +https://discord.gg/f6spsMA + + +Twitch: +https://twitch.tv/SamitoFPS + + +#Overwatch2 #LiveStream #ad + + + + + + + + yt:video:Grr3dX1NX8U + Grr3dX1NX8U + UC5Uxq95L6K60XVdNPmUxoYA + Actual Solutions to 5v5 and 6v6 in Overwatch 2 - (with Spilo) + + + Samito + https://www.youtube.com/channel/UC5Uxq95L6K60XVdNPmUxoYA + + 2024-05-30T21:16:25+00:00 + 2024-06-05T01:56:26+00:00 + + Actual Solutions to 5v5 and 6v6 in Overwatch 2 - (with Spilo) + + + Check out @StormcrowProductions + +Leave a like if you enjoyed & subscribe for more: http://bit.ly/2ttxnC0 + +Business inquiries ONLY (Private coaching is not a business inquiry!): +samito@loadscreen.gg + + +Stream Schedule: +Mon-Friday @ 11 AM EST ON THIS CHANNEL! + +Follow me on Twitter: +https://www.twitter.com/SamitoFPS + +Join my discord! +https://discord.gg/f6spsMA + + +Twitch: +https://twitch.tv/SamitoFPS + +Edited by: + +#Overwatch + + + + + + + + yt:video:NAT9jggf1Q8 + NAT9jggf1Q8 + UC5Uxq95L6K60XVdNPmUxoYA + OVERWATCH 2 LEARNING NEW PHARAH DAY 2 !PATREON !AD + + + Samito + https://www.youtube.com/channel/UC5Uxq95L6K60XVdNPmUxoYA + + 2024-05-30T19:29:02+00:00 + 2024-06-04T22:08:58+00:00 + + OVERWATCH 2 LEARNING NEW PHARAH DAY 2 !PATREON !AD + + + πŸš€ Install Star Trek Fleet Command for FREE now https://t2m.io/STFC_Samito and enter the promo code WARPSPEED to unlock 10 Epic Shards of Kirk, enhancing your command instantly! + +Leave a like if you enjoyed & subscribe for more: http://bit.ly/2ttxnC0 + +Business inquiries ONLY (Private coaching is not a business inquiry!): +samito@loadscreen.gg + +Stream Schedule: +Mon-Friday @ 11 AM EST ON THIS CHANNEL! + +Follow me on Twitter: +https://www.twitter.com/SamitoFPS + + +Join my discord! +https://discord.gg/f6spsMA + + +Twitch: +https://twitch.tv/SamitoFPS + + +#Overwatch2 #LiveStream #ad + + + + + + + + yt:video:l7X8x8Ec8tE + l7X8x8Ec8tE + UC5Uxq95L6K60XVdNPmUxoYA + OVERWATCH 2 LEARNING NEW PHARAH DAY 1 !PATREON !AD + + + Samito + https://www.youtube.com/channel/UC5Uxq95L6K60XVdNPmUxoYA + + 2024-05-29T19:19:48+00:00 + 2024-06-01T04:51:56+00:00 + + OVERWATCH 2 LEARNING NEW PHARAH DAY 1 !PATREON !AD + + + πŸš€ Install Star Trek Fleet Command for FREE now https://t2m.io/STFC_Samito and enter the promo code WARPSPEED to unlock 10 Epic Shards of Kirk, enhancing your command instantly! + +Leave a like if you enjoyed & subscribe for more: http://bit.ly/2ttxnC0 + +Business inquiries ONLY (Private coaching is not a business inquiry!): +samito@loadscreen.gg + +Stream Schedule: +Mon-Friday @ 11 AM EST ON THIS CHANNEL! + +Follow me on Twitter: +https://www.twitter.com/SamitoFPS + + +Join my discord! +https://discord.gg/f6spsMA + + +Twitch: +https://twitch.tv/SamitoFPS + + +#Overwatch2 #LiveStream #ad + + + + + + + + yt:video:ksze1pinp78 + ksze1pinp78 + UC5Uxq95L6K60XVdNPmUxoYA + PLAT Support Needs a Reality Check - COACH ROASTS STUDENT | Overwatch 2 + + + Samito + https://www.youtube.com/channel/UC5Uxq95L6K60XVdNPmUxoYA + + 2024-05-29T14:40:08+00:00 + 2024-06-01T02:19:56+00:00 + + PLAT Support Needs a Reality Check - COACH ROASTS STUDENT | Overwatch 2 + + + Check out Patreon for coaching and community!!! https://www.patreon.com/samito27/membership + +Leave a like if you enjoyed & subscribe for more: http://bit.ly/2ttxnC0 + +Business inquiries ONLY (Private coaching is not a business inquiry!): +samito@loadscreen.gg + +Stream Schedule: +Mon-Friday @ 11 AM EST ON THIS CHANNEL! + +Follow me on Twitter: +https://www.twitter.com/SamitoFPS + + +Join my discord! +https://discord.gg/f6spsMA + + +Twitch: +https://twitch.tv/SamitoFPS + +Edited by: +https://twitter.com/senzeehpai + + +#Overwatch + + + + + + + + yt:video:uqkGZpJpCq8 + uqkGZpJpCq8 + UC5Uxq95L6K60XVdNPmUxoYA + When you need to convince the homie to download a new game + + + Samito + https://www.youtube.com/channel/UC5Uxq95L6K60XVdNPmUxoYA + + 2024-05-29T13:45:04+00:00 + 2024-06-01T21:42:26+00:00 + + When you need to convince the homie to download a new game + + + Leave a like if you enjoyed & subscribe for more: http://bit.ly/2ttxnC0 + +Business inquiries ONLY (Private coaching is not a business inquiry!): +samito@loadscreen.gg + + +Stream Schedule: +Mon-Friday @ 11 AM EST ON THIS CHANNEL! + +Follow me on Twitter: +https://www.twitter.com/SamitoFPS + + +Join my discord! +https://discord.gg/f6spsMA + + +Twitch: +https://twitch.tv/SamitoFPS + +Edited by: +https://twitter.com/senzeehpai + + +#Overwatch + + + + + + + + yt:video:vmq1ZpCKoCg + vmq1ZpCKoCg + UC5Uxq95L6K60XVdNPmUxoYA + Here's Why Marvel Rivals Changes Everything + + + Samito + https://www.youtube.com/channel/UC5Uxq95L6K60XVdNPmUxoYA + + 2024-05-28T16:29:53+00:00 + 2024-06-01T21:47:38+00:00 + + Here's Why Marvel Rivals Changes Everything + + + For PRIVATE 6v6 Scrim Server Access - https://www.patreon.com/samito27/membership + +Business inquiries ONLY (Private coaching is not a business inquiry!): +samito@loadscreen.gg + +Check out! Sprixy +https://youtu.be/GQHS9bD_IvQ?si=rsIYq5QjHnC4VywJ + +Stream Schedule: +Mon-Friday @ 11 AM EST ON THIS CHANNEL! + +Follow me on Twitter: +https://www.twitter.com/SamitoFPS + +Join my discord! +https://discord.gg/f6spsMA + +Twitch: +https://twitch.tv/SamitoFPS + +Edited by: +Shadin + +#Overwatch + + + + + + + + yt:video:23Fj9HaXqEA + 23Fj9HaXqEA + UC5Uxq95L6K60XVdNPmUxoYA + Support Mains are BOOSTED + + + Samito + https://www.youtube.com/channel/UC5Uxq95L6K60XVdNPmUxoYA + + 2024-05-28T13:45:00+00:00 + 2024-06-03T01:36:39+00:00 + + Support Mains are BOOSTED + + + Leave a like if you enjoyed & subscribe for more: http://bit.ly/2ttxnC0 + +Business inquiries ONLY (Private coaching is not a business inquiry!): +samito@loadscreen.gg + + +Stream Schedule: +Mon-Friday @ 11 AM EST ON THIS CHANNEL! + +Follow me on Twitter: +https://www.twitter.com/SamitoFPS + + +Join my discord! +https://discord.gg/f6spsMA + + +Twitch: +https://twitch.tv/SamitoFPS + +Edited by: +https://twitter.com/senzeehpai + + +#Overwatch + + + + + + + + yt:video:IYGs54YQw9s + IYGs54YQw9s + UC5Uxq95L6K60XVdNPmUxoYA + Samito Might be a Little Mad + + + Samito + https://www.youtube.com/channel/UC5Uxq95L6K60XVdNPmUxoYA + + 2024-05-27T13:00:13+00:00 + 2024-05-29T11:02:38+00:00 + + Samito Might be a Little Mad + + + Leave a like if you enjoyed & subscribe for more: http://bit.ly/2ttxnC0 + +Business inquiries ONLY (Private coaching is not a business inquiry!): +samito@loadscreen.gg + + +Stream Schedule: +Mon-Friday @ 11 AM EST ON THIS CHANNEL! + +Follow me on Twitter: +https://www.twitter.com/SamitoFPS + + +Join my discord! +https://discord.gg/f6spsMA + + +Twitch: +https://twitch.tv/SamitoFPS + +Edited by: +https://twitter.com/senzeehpai + + +#Overwatch + + + + + + + + yt:video:KF6FDX8ju9U + KF6FDX8ju9U + UC5Uxq95L6K60XVdNPmUxoYA + It's Time to STOP the Overwatch Shill Accusations (kinda) + + + Samito + https://www.youtube.com/channel/UC5Uxq95L6K60XVdNPmUxoYA + + 2024-05-26T17:46:29+00:00 + 2024-05-29T15:10:12+00:00 + + It's Time to STOP the Overwatch Shill Accusations (kinda) + + + Check out @GSRaider + +Leave a like if you enjoyed & subscribe for more: http://bit.ly/2ttxnC0 + +Business inquiries ONLY (Private coaching is not a business inquiry!): +samito@loadscreen.gg + + +Stream Schedule: +Mon-Friday @ 11 AM EST ON THIS CHANNEL! + +Follow me on Twitter: +https://www.twitter.com/SamitoFPS + +Join my discord! +https://discord.gg/f6spsMA + + +Twitch: +https://twitch.tv/SamitoFPS + +Edited by: +https://twitter.com/senzeehpai + + +#Overwatch + + + + + + + + yt:video:eWpanQXLPTY + eWpanQXLPTY + UC5Uxq95L6K60XVdNPmUxoYA + OVERWATCH 2 NEW TANK CHANGES FUN ROLE!! Star Trek Fleet Command Later !AD !PATREON + + + Samito + https://www.youtube.com/channel/UC5Uxq95L6K60XVdNPmUxoYA + + 2024-05-25T21:00:39+00:00 + 2024-05-31T22:17:49+00:00 + + OVERWATCH 2 NEW TANK CHANGES FUN ROLE!! Star Trek Fleet Command Later !AD !PATREON + + + πŸš€ Install Star Trek Fleet Command for FREE now https://t2m.io/STFC_Samito and enter the promo code WARPSPEED to unlock 10 Epic Shards of Kirk, enhancing your command instantly! + +Leave a like if you enjoyed & subscribe for more: http://bit.ly/2ttxnC0 + +Business inquiries ONLY (Private coaching is not a business inquiry!): +samito@loadscreen.gg + +Stream Schedule: +Mon-Friday @ 11 AM EST ON THIS CHANNEL! + +Follow me on Twitter: +https://www.twitter.com/SamitoFPS + + +Join my discord! +https://discord.gg/f6spsMA + + +Twitch: +https://twitch.tv/SamitoFPS + + +#Overwatch2 #LiveStream #ad + + + + + + + diff --git a/feed/testdata/c.xml b/feed/testdata/c.xml new file mode 100644 index 0000000..1240a37 --- /dev/null +++ b/feed/testdata/c.xml @@ -0,0 +1,491 @@ + + + + yt:channel:X6OQ3DkcsbYNE6H8uQQuVA + X6OQ3DkcsbYNE6H8uQQuVA + MrBeast + + + MrBeast + https://www.youtube.com/channel/UCX6OQ3DkcsbYNE6H8uQQuVA + + 2012-02-20T00:43:50+00:00 + + yt:video:U_LlX4t0A9I + U_LlX4t0A9I + UCX6OQ3DkcsbYNE6H8uQQuVA + $10,000 Every Day You Survive In The Wilderness + + + MrBeast + https://www.youtube.com/channel/UCX6OQ3DkcsbYNE6H8uQQuVA + + 2024-06-01T16:00:00+00:00 + 2024-06-05T16:22:24+00:00 + + $10,000 Every Day You Survive In The Wilderness + + + They survived longer than I expected lol +Feast like a Beast with Zaxby’s new MrBeast Box, served with 4 Chicken Fingerzβ„’, cheddar bites, fries, double Texas toast, two signature sauces, small drink…and a milk chocolate FEASTABLES BAR! Now available at Zaxby's for a limited time. Order today: https://www.zaxbys.com/mrbeast + +Want to check out the Galaxy S24? Head over to Samsung https://smsng.us/MrBS24 + +Please make sure you have an email address on your profile! +We will be reaching out from giveaway@mrbeastbusiness.com if you are selected as a winner! + +New Merch - https://mrbeast.store + +Check out Viewstats! - https://www.viewstats.com/ + +SUBSCRIBE OR I TAKE YOUR DOG +╔═╦╗╔╦╗╔═╦═╦╦╦╦╗╔═╗ +β•‘β•šβ•£β•‘β•‘β•‘β•šβ•£β•šβ•£β•”β•£β•”β•£β•‘β•šβ•£β•β•£ +β• β•—β•‘β•šβ•β•‘β•‘β• β•—β•‘β•šβ•£β•‘β•‘β•‘β•‘β•‘β•β•£ +β•šβ•β•©β•β•β•©β•β•©β•β•©β•β•©β•β•šβ•©β•β•©β•β• + +For any questions or inquiries regarding this video, please reach out to chucky@mrbeastbusiness.com + +Music Provided by https://www.extrememusic.com + +---------------------------------------------------------------- +follow all of these or i will kick you +β€’ Facebook - https://www.facebook.com/MrBeast6000/ +β€’ Twitter - https://twitter.com/MrBeast +β€’ Instagram - https://www.instagram.com/mrbeast +β€’ Im Hiring! - https://www.mrbeastjobs.com/ +-------------------------------------------------------------------- + + + + + + + + yt:video:T8I165Qxeo8 + T8I165Qxeo8 + UCX6OQ3DkcsbYNE6H8uQQuVA + Sprinting with More and More Money + + + MrBeast + https://www.youtube.com/channel/UCX6OQ3DkcsbYNE6H8uQQuVA + + 2024-05-30T16:00:01+00:00 + 2024-06-05T16:20:45+00:00 + + Sprinting with More and More Money + + + + + + + + + + + yt:video:i-9V21MqlhY + i-9V21MqlhY + UCX6OQ3DkcsbYNE6H8uQQuVA + Giving 1000 Phones Away + + + MrBeast + https://www.youtube.com/channel/UCX6OQ3DkcsbYNE6H8uQQuVA + + 2024-05-28T16:00:00+00:00 + 2024-06-04T10:10:03+00:00 + + Giving 1000 Phones Away + + + + + + + + + + + yt:video:f0cXJ6mJxGc + f0cXJ6mJxGc + UCX6OQ3DkcsbYNE6H8uQQuVA + Bottle Head Smashing World Record Attempt! + + + MrBeast + https://www.youtube.com/channel/UCX6OQ3DkcsbYNE6H8uQQuVA + + 2024-05-14T16:00:00+00:00 + 2024-05-31T18:41:15+00:00 + + Bottle Head Smashing World Record Attempt! + + + + + + + + + + + yt:video:F6PqxbvOCUI + F6PqxbvOCUI + UCX6OQ3DkcsbYNE6H8uQQuVA + Protect The Yacht, Keep It! + + + MrBeast + https://www.youtube.com/channel/UCX6OQ3DkcsbYNE6H8uQQuVA + + 2024-05-11T16:00:00+00:00 + 2024-05-17T01:09:30+00:00 + + Protect The Yacht, Keep It! + + + This video did not go how I expected it to +Craft your perfect defense from a combination of powerful Monkey Towers and awesome Heroes, then pop every last invading Bloon! Grab your copy of Bloons TD 6 now - https://ninja.kiwi/mrbeast + +Play the Official Fortnite Creative Map For This Video Here: +MrBeast Bed Wars πŸ›Œ - 5948-5884-5269 +https://www.fortnite.com/@mrbeast/5948-5884-5269 + +New Merch - https://mrbeast.store + +Check out Viewstats! - https://www.viewstats.com/ + +SUBSCRIBE OR I TAKE YOUR DOG +╔═╦╗╔╦╗╔═╦═╦╦╦╦╗╔═╗ +β•‘β•šβ•£β•‘β•‘β•‘β•šβ•£β•šβ•£β•”β•£β•”β•£β•‘β•šβ•£β•β•£ +β• β•—β•‘β•šβ•β•‘β•‘β• β•—β•‘β•šβ•£β•‘β•‘β•‘β•‘β•‘β•β•£ +β•šβ•β•©β•β•β•©β•β•©β•β•©β•β•©β•β•šβ•©β•β•©β•β• + +For any questions or inquiries regarding this video, please reach out to chucky@mrbeastbusiness.com + +Music Provided by https://www.extrememusic.com + +---------------------------------------------------------------- +follow all of these or i will kick you +β€’ Facebook - https://www.facebook.com/MrBeast6000/ +β€’ Twitter - https://twitter.com/MrBeast +β€’ Instagram - https://www.instagram.com/mrbeast +β€’ Im Hiring! - https://www.mrbeastjobs.com/ +-------------------------------------------------------------------- + + + + + + + + yt:video:oA4LSZvX4iE + oA4LSZvX4iE + UCX6OQ3DkcsbYNE6H8uQQuVA + Ages 1-100 Try My Chocolate + + + MrBeast + https://www.youtube.com/channel/UCX6OQ3DkcsbYNE6H8uQQuVA + + 2024-05-09T16:00:00+00:00 + 2024-05-18T22:48:01+00:00 + + Ages 1-100 Try My Chocolate + + + + + + + + + + + yt:video:ZKxnjszkZto + ZKxnjszkZto + UCX6OQ3DkcsbYNE6H8uQQuVA + Spot The Hidden People For $10,000 + + + MrBeast + https://www.youtube.com/channel/UCX6OQ3DkcsbYNE6H8uQQuVA + + 2024-05-08T16:00:01+00:00 + 2024-05-25T01:59:55+00:00 + + Spot The Hidden People For $10,000 + + + + + + + + + + + yt:video:8_gdcaX9Xqk + 8_gdcaX9Xqk + UCX6OQ3DkcsbYNE6H8uQQuVA + Would You Split Or Steal $250,000? + + + MrBeast + https://www.youtube.com/channel/UCX6OQ3DkcsbYNE6H8uQQuVA + + 2024-05-03T16:00:00+00:00 + 2024-05-08T21:04:20+00:00 + + Would You Split Or Steal $250,000? + + + + + + + + + + + yt:video:Pv0iVoSZzN8 + Pv0iVoSZzN8 + UCX6OQ3DkcsbYNE6H8uQQuVA + In 10 Minutes This Room Will Explode! + + + MrBeast + https://www.youtube.com/channel/UCX6OQ3DkcsbYNE6H8uQQuVA + + 2024-04-27T16:00:00+00:00 + 2024-05-22T15:56:00+00:00 + + In 10 Minutes This Room Will Explode! + + + I didn’t expect that to happen… +There’s no jumping through hoops (or plate glass windows) with T-Mobile. Customers can get Magenta Status from day one, including hotel discounts on select brands, deals on rental cars, discounts on select concert tickets nationwide, and so much more. See how at https://t-mobile.com/status + +New Merch - https://mrbeast.store + +Check out Viewstats! - https://www.viewstats.com/ + +SUBSCRIBE OR I TAKE YOUR DOG +╔═╦╗╔╦╗╔═╦═╦╦╦╦╗╔═╗ +β•‘β•šβ•£β•‘β•‘β•‘β•šβ•£β•šβ•£β•”β•£β•”β•£β•‘β•šβ•£β•β•£ +β• β•—β•‘β•šβ•β•‘β•‘β• β•—β•‘β•šβ•£β•‘β•‘β•‘β•‘β•‘β•β•£ +β•šβ•β•©β•β•β•©β•β•©β•β•©β•β•©β•β•šβ•©β•β•©β•β• + +For any questions or inquiries regarding this video, please reach out to chucky@mrbeastbusiness.com + +Music Provided by https://www.extrememusic.com + +---------------------------------------------------------------- +follow all of these or i will kick you +β€’ Facebook - https://www.facebook.com/MrBeast6000/ +β€’ Twitter - https://twitter.com/MrBeast +β€’ Instagram - https://www.instagram.com/mrbeast +β€’ Im Hiring! - https://www.mrbeastjobs.com/ +-------------------------------------------------------------------- + + + + + + + + yt:video:CWbV3NItSdY + CWbV3NItSdY + UCX6OQ3DkcsbYNE6H8uQQuVA + The World's Fastest Cleaners + + + MrBeast + https://www.youtube.com/channel/UCX6OQ3DkcsbYNE6H8uQQuVA + + 2024-04-25T16:00:03+00:00 + 2024-05-30T02:48:16+00:00 + + The World's Fastest Cleaners + + + + + + + + + + + yt:video:l-nMKJ5J3Uc + l-nMKJ5J3Uc + UCX6OQ3DkcsbYNE6H8uQQuVA + Ages 1 - 100 Decide Who Wins $250,000 + + + MrBeast + https://www.youtube.com/channel/UCX6OQ3DkcsbYNE6H8uQQuVA + + 2024-04-20T15:59:59+00:00 + 2024-05-21T18:41:00+00:00 + + Ages 1 - 100 Decide Who Wins $250,000 + + + I can’t believe who they picked +Thanks Top Troops for sponsoring this video. Download Top Troops at https://toptroops.onelink.me/JHnC/mrbeast or scan the QR code to get a FREE special Starter Pack for a limited time + +Thanks Samsung for the Tab S9s - the Galaxy AI features are cool too: https://smsng.us/MrBTabS9 + +New Merch - https://mrbeast.store + +Check out Viewstats! - https://www.viewstats.com/ + +SUBSCRIBE OR I TAKE YOUR DOG +╔═╦╗╔╦╗╔═╦═╦╦╦╦╗╔═╗ +β•‘β•šβ•£β•‘β•‘β•‘β•šβ•£β•šβ•£β•”β•£β•”β•£β•‘β•šβ•£β•β•£ +β• β•—β•‘β•šβ•β•‘β•‘β• β•—β•‘β•šβ•£β•‘β•‘β•‘β•‘β•‘β•β•£ +β•šβ•β•©β•β•β•©β•β•©β•β•©β•β•©β•β•šβ•©β•β•©β•β• + +For any questions or inquiries regarding this video, please reach out to chucky@mrbeastbusiness.com + +Also thank you Concept Pixel for the LED Walls,it looked great! +Β www.conceptpixels.com + +Music Provided by https://www.extrememusic.com + +---------------------------------------------------------------- +follow all of these or i will kick you +β€’ Facebook - https://www.facebook.com/MrBeast6000/ +β€’ Twitter - https://twitter.com/MrBeast +β€’ Instagram - https://www.instagram.com/mrbeast +β€’ Im Hiring! - https://www.mrbeastjobs.com/ +-------------------------------------------------------------------- + + + + + + + + yt:video:XE1Qyss8GIY + XE1Qyss8GIY + UCX6OQ3DkcsbYNE6H8uQQuVA + Guess The Gift, Keep It + + + MrBeast + https://www.youtube.com/channel/UCX6OQ3DkcsbYNE6H8uQQuVA + + 2024-04-09T17:00:00+00:00 + 2024-05-20T17:49:32+00:00 + + Guess The Gift, Keep It + + + + + + + + + + + yt:video:6GzHPS0rEgc + 6GzHPS0rEgc + UCX6OQ3DkcsbYNE6H8uQQuVA + I’m Giving My 250M Subscriber $25,000 + + + MrBeast + https://www.youtube.com/channel/UCX6OQ3DkcsbYNE6H8uQQuVA + + 2024-04-06T19:45:00+00:00 + 2024-05-25T21:32:48+00:00 + + I’m Giving My 250M Subscriber $25,000 + + + + + + + + + + + yt:video:imhY0pe-Sd8 + imhY0pe-Sd8 + UCX6OQ3DkcsbYNE6H8uQQuVA + Anything You Touch, You Keep! + + + MrBeast + https://www.youtube.com/channel/UCX6OQ3DkcsbYNE6H8uQQuVA + + 2024-04-02T16:00:03+00:00 + 2024-05-12T21:41:57+00:00 + + Anything You Touch, You Keep! + + + + + + + + + + + yt:video:erLbbextvlY + erLbbextvlY + UCX6OQ3DkcsbYNE6H8uQQuVA + 7 Days Stranded On An Island + + + MrBeast + https://www.youtube.com/channel/UCX6OQ3DkcsbYNE6H8uQQuVA + + 2024-03-30T16:00:01+00:00 + 2024-05-25T23:02:55+00:00 + + 7 Days Stranded On An Island + + + I can’t believe we actually did this +Send money around the world with Western Union. New customers get their first online transaction fee free. https://www.westernunion.com/mrbeast . Restrictions and FX gains apply. + +Play the Official Fortnite Creative Map For This Video Here: +MrBeast Zombie Island 🏝️ - 4231-6172-1988 +https://www.fortnite.com/@mrbeast/4231-6172-1988 + +New Merch - https://mrbeast.store + +Check out Viewstats! - https://www.viewstats.com/ + +SUBSCRIBE OR I TAKE YOUR DOG +╔═╦╗╔╦╗╔═╦═╦╦╦╦╗╔═╗ +β•‘β•šβ•£β•‘β•‘β•‘β•šβ•£β•šβ•£β•”β•£β•”β•£β•‘β•šβ•£β•β•£ +β• β•—β•‘β•šβ•β•‘β•‘β• β•—β•‘β•šβ•£β•‘β•‘β•‘β•‘β•‘β•β•£ +β•šβ•β•©β•β•β•©β•β•©β•β•©β•β•©β•β•šβ•©β•β•©β•β• + +For any questions or inquiries regarding this video, please reach out to chucky@mrbeastbusiness.com + +Music Provided by https://www.extrememusic.com + +---------------------------------------------------------------- +follow all of these or i will kick you +β€’ Facebook - https://www.facebook.com/MrBeast6000/ +β€’ Twitter - https://twitter.com/MrBeast +β€’ Instagram - https://www.instagram.com/mrbeast +β€’ Im Hiring! - https://www.mrbeastjobs.com/ +-------------------------------------------------------------------- + + + + + + + diff --git a/feed/testdata/feedlink.txt b/feed/testdata/feedlink.txt new file mode 100644 index 0000000..2d2c0aa --- /dev/null +++ b/feed/testdata/feedlink.txt @@ -0,0 +1 @@ +http://www.youtube.com/feeds/videos.xml?channel_id=UCX6OQ3DkcsbYNE6H8uQQuVA diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..98c5d38 --- /dev/null +++ b/go.mod @@ -0,0 +1,23 @@ +module gitea.hbanafa.com/hesham/yttopodcast + +go 1.22.2 + +require ( + github.com/lrstanley/go-ytdlp v0.0.0-20240504025846-c0493251b060 + github.com/mmcdole/gofeed v1.3.0 +) + +require ( + github.com/ProtonMail/go-crypto v1.0.0 // indirect + github.com/PuerkitoBio/goquery v1.8.0 // indirect + github.com/andybalholm/cascadia v1.3.1 // indirect + github.com/cloudflare/circl v1.3.7 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/mmcdole/goxpp v1.1.1-0.20240225020742-a0c311522b23 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + golang.org/x/crypto v0.21.0 // indirect + golang.org/x/net v0.21.0 // indirect + golang.org/x/sys v0.18.0 // indirect + golang.org/x/text v0.14.0 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..aeb3405 --- /dev/null +++ b/go.sum @@ -0,0 +1,87 @@ +github.com/ProtonMail/go-crypto v1.0.0 h1:LRuvITjQWX+WIfr930YHG2HNfjR1uOfyf5vE0kC2U78= +github.com/ProtonMail/go-crypto v1.0.0/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0= +github.com/PuerkitoBio/goquery v1.8.0 h1:PJTF7AmFCFKk1N6V6jmKfrNH9tV5pNE6lZMkG0gta/U= +github.com/PuerkitoBio/goquery v1.8.0/go.mod h1:ypIiRMtY7COPGk+I/YbZLbxsxn9g5ejnI2HSMtkjZvI= +github.com/andybalholm/cascadia v1.3.1 h1:nhxRkql1kdYCc8Snf7D5/D3spOX+dBgjA6u8x004T2c= +github.com/andybalholm/cascadia v1.3.1/go.mod h1:R4bJ1UQfqADjvDa4P6HZHLh/3OxWWEqc0Sk8XGwHqvA= +github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= +github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA= +github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU= +github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/lrstanley/go-ytdlp v0.0.0-20240504025846-c0493251b060 h1:UOZcZVKXvw5ZcQ/shW/7xonMJYib9n9FKyNs/TAYAKc= +github.com/lrstanley/go-ytdlp v0.0.0-20240504025846-c0493251b060/go.mod h1:75ujbafjqiJugIGw4K6o52/p8C0m/kt+DrYwgClXYT4= +github.com/mmcdole/gofeed v1.3.0 h1:5yn+HeqlcvjMeAI4gu6T+crm7d0anY85+M+v6fIFNG4= +github.com/mmcdole/gofeed v1.3.0/go.mod h1:9TGv2LcJhdXePDzxiuMnukhV2/zb6VtnZt1mS+SjkLE= +github.com/mmcdole/goxpp v1.1.1-0.20240225020742-a0c311522b23 h1:Zr92CAlFhy2gL+V1F+EyIuzbQNbSgP4xhTODZtrXUtk= +github.com/mmcdole/goxpp v1.1.1-0.20240225020742-a0c311522b23/go.mod h1:v+25+lT2ViuQ7mVxcncQ8ch1URund48oH+jhjiwEgS8= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= +golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= +golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= +golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210916014120-12bc252f5db8/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= +golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4= +golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= +golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/templates/base.rss.templ b/templates/base.rss.templ new file mode 100644 index 0000000..a006f6e --- /dev/null +++ b/templates/base.rss.templ @@ -0,0 +1,54 @@ + + + + + {{ .Title }} + {{ .PublishDateRfcEmail }} + {{ .BuildDateRfcEmail }} + {{ .GeneratorName }} + {{ .PodcastPage }} + {{ .Lang }} + + + + {{ .PodcastPage }} + + + {{ .PodcastImageURL }} + {{ .Title }} + + + {{ .Title }} + Youtube,{{ .Title }} + + + + + + + false + + + mail@mail.none + + + episodic + no{{ range .Items }} + + <![CDATA[{{ .Title }}]]> + + {{ .PublishDateRfcEmail }} + + + + + + + {{ .Duration }} + false + + + full + {{ end }} + + diff --git a/templates/item.rss.templ b/templates/item.rss.templ new file mode 100644 index 0000000..b3d9feb --- /dev/null +++ b/templates/item.rss.templ @@ -0,0 +1,17 @@ + + {{ .Title }} + {{ .Title }} + {{ .PublishDateRfcEmail }} + + + + + + + {{ .Duration }} + false + + + full + + diff --git a/templates/types.go b/templates/types.go new file mode 100644 index 0000000..c1b42b5 --- /dev/null +++ b/templates/types.go @@ -0,0 +1,36 @@ +package templates + +import ( + "embed" +) + +//go:embed *.templ +var TemplatesFS embed.FS + +//go:embed base.rss.templ +var RSSTemplate string + +type FeedData struct { + Title string + PublishDateRfcEmail string + BuildDateRfcEmail string + GeneratorName string + PodcastPage string + Lang string + CopyRight string + Summary string + PodcastImageURL string + FeedURL string + Items []FeedItem +} + +type FeedItem struct { + Title string + PublishDateRfcEmail string + Id string + CoverImageURL string + Description string + Length int + EnclosureURL string + Duration string +} diff --git a/ytlinkprov/cache_provider.go b/ytlinkprov/cache_provider.go new file mode 100644 index 0000000..6879a57 --- /dev/null +++ b/ytlinkprov/cache_provider.go @@ -0,0 +1,80 @@ +package ytlinkprov + +import ( + "context" + "fmt" + "time" + + "github.com/lrstanley/go-ytdlp" +) + +type CacheLinkProv struct { + cache map[string]TimedLink + cacheWindow time.Duration + ytInstall ytdlp.ResolvedInstall +} + +type TimedLink struct { + Link string + Time time.Time +} + +func NewCachedLinkProvider(expiration time.Duration) (*CacheLinkProv, error) { + p := new(CacheLinkProv) + p.cache = make(map[string]TimedLink) + p.cacheWindow = expiration + ctx := context.Background() + ytInstall, err := ytdlp.Install( + ctx, + &ytdlp.InstallOptions{ + AllowVersionMismatch: true, + }, + ) + if err != nil { + return p, err + } + p.ytInstall = *ytInstall + + return p, nil +} + +func (c *CacheLinkProv) GetLink(id string) (link string, err error) { + + cc, ok := c.cache[id] + if ok && c.validCache(cc) { + return cc.Link, nil + } + link, err = getRemoteLink(id) + if err != nil { + return "", err + } + t_now := time.Now().UTC() + c.cache[id] = TimedLink{ + Link: link, + Time: t_now, + } + + // INFO: This is a vary, vary slow leak + + return link, nil +} + +func (c *CacheLinkProv) validCache(l TimedLink) bool { + t_exp := time.Now().UTC().Add(c.cacheWindow) + if t_exp.Before(l.Time) { + // expired + return false + } + return true + +} + +func getRemoteLink(id string) (string, error) { + vidUrl := fmt.Sprintf("https://youtube.com/watch?v=%s", id) + ytCmd := ytdlp.New().ExtractAudio().GetURL() + ytRes, err := ytCmd.Run(context.Background(), vidUrl) + if err != nil { + return "", err + } + return ytRes.Stdout, nil +} diff --git a/ytlinkprov/common.go b/ytlinkprov/common.go new file mode 100644 index 0000000..721eb5a --- /dev/null +++ b/ytlinkprov/common.go @@ -0,0 +1,6 @@ +package ytlinkprov + +type YtLinkProvider interface { + GetLink(id string) (link string, err error) +} +