package lib import ( "fmt" "log" "os" "time" ) type Runner struct { youtube *Youtube facebook *Facebook linkedin *Linkedin cache *Cache } func NewRunner() (*Runner, error) { channelID := os.Getenv("YOUTUBE_CHANNEL_ID") if channelID == "" { return nil, fmt.Errorf("YOUTUBE_CHANNEL_ID is required") } cacheFile := os.Getenv("CACHE_FILE_PATH") if cacheFile == "" { cacheFile = "/data/cache.csv" } cache, err := NewCache(cacheFile) if err != nil { return nil, fmt.Errorf("cache init: %w", err) } return &Runner{ youtube: NewYoutube(channelID), facebook: NewFacebook(os.Getenv("FACEBOOK_ACCESS_TOKEN"), os.Getenv("FACEBOOK_PAGE_ID")), linkedin: NewLinkedin(os.Getenv("LINKEDIN_ACCESS_TOKEN"), os.Getenv("LINKEDIN_AUTHOR_URN")), cache: cache, }, nil } func (r *Runner) Run() { r.process() ticker := time.NewTicker(time.Hour) defer ticker.Stop() for range ticker.C { r.process() } } func (r *Runner) process() { log.Println("checking for new YouTube videos") videos, err := r.youtube.FetchLatest() if err != nil { log.Printf("youtube fetch error: %v", err) return } for _, video := range videos { if r.cache.Has(video.ID) { continue } log.Printf("new video: %s", video.Title) if err := r.facebook.Post(video); err != nil { log.Printf("facebook post error: %v", err) } if err := r.linkedin.Post(video); err != nil { log.Printf("linkedin post error: %v", err) } if err := r.cache.Add(video.ID); err != nil { log.Printf("cache write error: %v", err) } } }