package main import ( "encoding/xml" "fmt" "io" "log" "net/http" "os" "regexp" "sync" "time" ) type DownloadRequest struct { Link string `json:"link"` } type RssWatcher struct { Feed *RssFeed } var videoRegex = regexp.MustCompile(`yt:video:(?[^ ]+) (?:[^ ]+ ){2}(?.+?)https(?:[^ ]+ ){2}(?[^ ]+)`) var feeds []*RssFeed = []*RssFeed{ {Url: "https://www.youtube.com/feeds/videos.xml?channel_id=UCMwJJL5FJFuTRT55ksbQ4GQ", Id: "@AsmongoldClips"}, {Url: "https://www.youtube.com/feeds/videos.xml?channel_id=UC8nZUXCwCTffxthKLtOp6ng", Id: "@Splattercatgaming"}, {Url: "https://www.youtube.com/feeds/videos.xml?channel_id=UC2THf0jmDDeBujMzG1sD2-Q", Id: "@thesingleplayersquad"}, {Url: "https://www.youtube.com/feeds/videos.xml?channel_id=UCmtyQOKKmrMVaKuRXz02jbQ", Id: "@SebastianLague"}, {Url: "https://www.youtube.com/feeds/videos.xml?channel_id=UCywBfpGBYhsczNuyyh6Cf6w", Id: "@WorthABuyreviews"}, } func (w *RssWatcher) Watch(videoUrls chan string) error { ticker := time.NewTicker(1 * time.Minute) defer ticker.Stop() log.Printf("[%s]: Watcher started, checking every minute.", w.Feed.Id) w.CheckFeed(videoUrls) for { select { case <-ticker.C: log.Printf("[%s]: Checking feed", w.Feed.Id) err := w.CheckFeed(videoUrls) if err != nil { return fmt.Errorf("watcher %s failed to check feed: %w", w.Feed.Id, err) } log.Printf("[%s]: Successfully checked feed", w.Feed.Id) } } } func (w *RssWatcher) CheckFeed(videoUrls chan string) error { log.Printf("Checking feed URL: %s", w.Feed.Url) resp, err := http.Get(w.Feed.Url) if err != nil { return fmt.Errorf("[%s]: failed to create request: %w", w.Feed.Id, err) } defer resp.Body.Close() log.Printf("Received response with status code: %d", resp.StatusCode) body, err := io.ReadAll(resp.Body) if err != nil { return fmt.Errorf("[%s]: failed to read response body: %w", w.Feed.Id, err) } // os.WriteFile("cache.xml", body, 0644) // body, err := os.ReadFile("cache.xml") // if err != nil { // return fmt.Errorf("[%s]: failed to read cache file: %w", w.Feed.Id, err) // } var feed Feed err = xml.Unmarshal(body, &feed) if err != nil { return fmt.Errorf("[%s]: failed to unmarshal feed: %w", w.Feed.Id, err) } for _, entry := range feed.Entry { uploaded, err := time.Parse(time.RFC3339, entry.Published) if err != nil { return fmt.Errorf("[%s]: failed to parse published date: %w", w.Feed.Id, err) } if uploaded.Before(w.Feed.LastSeen) { log.Printf("[%s]: Skipping video titled %q because it was uploaded before %s", w.Feed.Id, entry.Title, w.Feed.LastSeen.Format(time.RFC3339)) continue } log.Printf("[%s]: Found new video titled %q with url %q", w.Feed.Id, entry.Title, entry.Link.Href) videoUrls <- entry.Link.Href } err = w.Feed.WriteLastSeen(time.Now()) if err != nil { return fmt.Errorf("[%s]: failed to write last seen: %w", w.Feed.Id, err) } w.Feed.LastSeen = time.Now() return nil } var Error *log.Logger var Warning *log.Logger func init() { log.SetFlags(log.Lmicroseconds | log.Lshortfile) logFile, err := os.Create("ywatcher.log") if err != nil { log.Printf("Error creating log file: %v", err) os.Exit(1) } logger := io.MultiWriter(os.Stdout, logFile) log.SetOutput(logger) Error = log.New(io.MultiWriter(logFile, os.Stderr, os.Stdout), fmt.Sprintf("%sERROR:%s ", "\033[0;101m", "\033[0m"), log.Lmicroseconds|log.Lshortfile) Warning = log.New(io.MultiWriter(logFile, os.Stdout), fmt.Sprintf("%sWarning:%s ", "\033[0;93m", "\033[0m"), log.Lmicroseconds|log.Lshortfile) } func main() { videoUrls := make(chan string, 12) wg := &sync.WaitGroup{} for _, feed := range feeds { wg.Add(1) go func(feed *RssFeed) { err := feed.UpdateLastSeen() if err != nil { Error.Printf("failed to update lastseen for feed %s: %v", feed.Id, err) panic(err) } defer wg.Done() watcher := RssWatcher{ Feed: feed, } err = watcher.Watch(videoUrls) if err != nil { Error.Printf("watcher %s failed to watch feed: %v", feed.Id, err) panic(err) } }(feed) } go func() { for videoUrl := range videoUrls { log.Printf("Got new video with url %q", videoUrl) err := Download(videoUrl) if err != nil { Error.Printf("failed to download video: %v", err) panic(err) } } }() wg.Wait() }