package main import ( "fmt" "os" "text/tabwriter" "github.com/fjurmanovic/sugarr/internal/model" "github.com/fjurmanovic/sugarr/internal/service" "github.com/fjurmanovic/sugarr/internal/utl/configs" "github.com/fjurmanovic/sugarr/internal/utl/server" "github.com/robfig/cron/v3" "github.com/rs/zerolog" "github.com/rs/zerolog/log" ) const version = "0.0.1" func main() { log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr}) cfg, err := configs.Load() if err != nil { log.Fatal().Err(err).Msg("Failed to load config") } printBanner(cfg) rec := service.NewRecommender(cfg) // Cron scheduler if cfg.CronEnabled { c := cron.New() if _, err := c.AddFunc(cfg.CronSchedule, func() { log.Info().Msg("Cron: running recommendation pipeline") recs, err := rec.Run() if err != nil { log.Error().Err(err).Msg("Cron: pipeline failed") return } if cfg.RadarrEnabled { rec.AutoRequest(recs) } logRecommendations(recs) }); err != nil { log.Fatal().Err(err).Msg("Invalid cron schedule") } c.Start() defer c.Stop() log.Info().Msgf("Cron scheduled: %s", cfg.CronSchedule) } // HTTP API — blocks until server exits if cfg.APIEnabled { if err := server.Start(cfg, rec); err != nil { log.Fatal().Err(err).Msg("HTTP server failed") } return } // One-shot mode: no API, no cron if !cfg.CronEnabled { recs, err := rec.Run() if err != nil { log.Fatal().Err(err).Msg("Recommendation pipeline failed") } printTable(recs) return } // Cron-only mode: block forever select {} } func printBanner(cfg *configs.Config) { log.Info().Msg("sugarr starting") log.Info().Msgf(" Version: %s", version) log.Info().Msgf(" Jellyfin: %s (user: %s)", cfg.JellyfinURL, cfg.JellyfinUserID) log.Info().Msgf(" LLM: enabled=%v model=%s", cfg.LLMEnabled, cfg.LLMModel) log.Info().Msgf(" Radarr: enabled=%v", cfg.RadarrEnabled) log.Info().Msgf(" Cron: enabled=%v schedule=%s", cfg.CronEnabled, cfg.CronSchedule) log.Info().Msgf(" API: enabled=%v port=%d", cfg.APIEnabled, cfg.APIPort) log.Info().Msgf(" Count: %d recommendations", cfg.RecommendCount) } func logRecommendations(recs []model.Recommendation) { log.Info().Msgf("Recommendations (%d):", len(recs)) for i, r := range recs { log.Info().Msgf(" %d. %s (%d) [tmdb:%d] — %s", i+1, r.Title, r.Year, r.TMDbID, r.Reason) } } func printTable(recs []model.Recommendation) { if len(recs) == 0 { fmt.Println("No recommendations found.") return } w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0) fmt.Fprintln(w, "#\tTitle\tYear\tTMDb ID\tReason") fmt.Fprintln(w, "-\t-----\t----\t-------\t------") for i, r := range recs { fmt.Fprintf(w, "%d\t%s\t%d\t%d\t%s\n", i+1, r.Title, r.Year, r.TMDbID, r.Reason) } w.Flush() }