package main import ( "context" "encoding/json" "flag" "fmt" "os" "github.com/gmgauthier/gostations/internal/config" playerpkg "github.com/gmgauthier/gostations/internal/player" "github.com/gmgauthier/gostations/internal/radio" "github.com/gmgauthier/gostations/internal/ui" ) var version string func showVersion() { fmt.Println(version) } func precheck() { p := "mpv" if v, err := config.Get("player.command"); err == nil && v != "" { p = v } if !playerpkg.IsInstalled(p) { fmt.Printf("%s is either not installed, or not on your $PATH. Cannot continue.\n", p) os.Exit(1) } } // runFind implements the "find" subcommand for scripting / non-interactive lookup. // Usage: gostations find [-c country] [-t tags] ... [-x] [-j] func runFind(args []string) { fs := flag.NewFlagSet("find", flag.ExitOnError) var ( name string country string state string tags string notok bool jsonOut bool ) fs.StringVar(&name, "n", "", "Station name (or identifier).") fs.StringVar(&country, "c", "", "Home country.") fs.StringVar(&state, "s", "", "Home state (if in the United States).") fs.StringVar(&tags, "t", "", "Tag (or comma-separated tag list)") fs.BoolVar(¬ok, "x", false, "If toggled, will show stations that are down") fs.BoolVar(&jsonOut, "j", false, "Output as JSON array (for scripting)") fs.BoolVar(&jsonOut, "json", false, "Output as JSON array (for scripting)") fs.Usage = func() { fmt.Fprintf(os.Stderr, "Usage: gostations find [options]\n\nOptions:\n") fs.PrintDefaults() } _ = fs.Parse(args) if err := config.Init(); err != nil { fmt.Fprintf(os.Stderr, "config: %v\n", err) os.Exit(1) } stations, err := radio.Search(context.Background(), name, country, state, tags, notok) if err != nil { fmt.Fprintf(os.Stderr, "search: %v\n", err) os.Exit(1) } if jsonOut { b, _ := json.MarshalIndent(stations, "", " ") fmt.Println(string(b)) return } // human/script friendly list (similar to old output) fmt.Println("...Found Stations...") for i, s := range stations { listing := fmt.Sprintf("%-40s %-5s %-5s %s", Short(s.Name, 40), s.Codec, s.Bitrate, s.Url) fmt.Printf("%d) %s\n", i+1, listing) } if len(stations) == 0 { fmt.Println("(no stations matched)") } } // runPlay implements the "play" subcommand for scripting direct playback. // It accepts the same search flags or a direct URL as first positional arg. // Plays the first match (or the URL) using the configured player and blocks. func runPlay(args []string) { fs := flag.NewFlagSet("play", flag.ExitOnError) var ( name string country string state string tags string notok bool ) fs.StringVar(&name, "n", "", "Station name (or identifier).") fs.StringVar(&country, "c", "", "Home country.") fs.StringVar(&state, "s", "", "Home state (if in the United States).") fs.StringVar(&tags, "t", "", "Tag (or comma-separated tag list)") fs.BoolVar(¬ok, "x", false, "If toggled, will show stations that are down") fs.Usage = func() { fmt.Fprintf(os.Stderr, "Usage: gostations play [options] [direct-url]\n\nOptions:\n") fs.PrintDefaults() } _ = fs.Parse(args) pos := fs.Args() if err := config.Init(); err != nil { fmt.Fprintf(os.Stderr, "config: %v\n", err) os.Exit(1) } precheck() var playURL string if len(pos) > 0 { playURL = pos[0] } else { stations, err := radio.Search(context.Background(), name, country, state, tags, notok) if err != nil { fmt.Fprintf(os.Stderr, "search: %v\n", err) os.Exit(1) } if len(stations) == 0 { fmt.Fprintln(os.Stderr, "no stations found to play") os.Exit(1) } playURL = stations[0].Url fmt.Printf("Playing first match: %s\n", stations[0].Name) } // Use legacy for now (cleaned); will use richer player in TUI/IPC later. pname := "mpv" if v, err := config.Get("player.command"); err == nil && v != "" { pname = v } opts := "" if v, err := config.Get("player.options"); err == nil { opts = v } leg := playerpkg.NewLegacy(pname, opts) if err := leg.Play(playURL); err != nil { fmt.Fprintf(os.Stderr, "play error: %v\n", err) os.Exit(1) } } func main() { // Early version / help for top level if len(os.Args) > 1 { switch os.Args[1] { case "find": runFind(os.Args[2:]) return case "play": runPlay(os.Args[2:]) return case "-v", "--version", "version": showVersion() return case "-h", "--help", "help": printTopHelp() return } } // Common flags (used for TUI seeding or legacy) var ( name string country string state string tags string notok bool legacy bool version bool ) fs := flag.NewFlagSet("gostations", flag.ExitOnError) fs.StringVar(&name, "n", "", "Station name (or identifier).") fs.StringVar(&country, "c", "", "Home country.") fs.StringVar(&state, "s", "", "Home state (if in the United States).") fs.StringVar(&tags, "t", "", "Tag (or comma-separated tag list)") fs.BoolVar(¬ok, "x", false, "If toggled, will show stations that are down") fs.BoolVar(&legacy, "legacy", false, "Force old wmenu UI (kept until new TUI is perfect)") fs.BoolVar(&version, "v", false, "Show version.") fs.Usage = printTopHelp _ = fs.Parse(os.Args[1:]) if version { showVersion() return } precheck() if err := config.Init(); err != nil { fmt.Printf("config init: %v\n", err) os.Exit(1) } stations, err := radio.Search(context.Background(), name, country, state, tags, notok) if err != nil { fmt.Printf("warning: station search: %v\n", err) stations = nil } if legacy { // Old wmenu path (gated) menu := RadioMenu(stations) if err := menu.Run(); err != nil { fmt.Println(err.Error()) os.Exit(1) } return } // Default: new TUI (Bubble Tea) if err := ui.Run(stations); err != nil { fmt.Printf("TUI error: %v\n", err) os.Exit(1) } } func printTopHelp() { fmt.Printf(`Usage: gostations [options] # Launch interactive TUI (default) gostations find [options] # Non-interactive station search (scripting) gostations play [options] [url] # Direct playback (scripting) gostations -h | --help gostations -v | --version Global options: -n string Station name (or identifier). -c string Home country. -s string Home state (if in the United States). -t string Tag (or comma-separated tag list) -x If toggled, will show stations that are down -v Show version. --legacy Force old wmenu UI (temporary) Subcommand examples: gostations find -c "United Kingdom" -t "news" -j gostations play -c "Gambia" gostations play http://stream.example.com/radio.mp3 Old top-level behavior without subcommand now defaults to the new TUI. Use --legacy to force the classic wmenu flow. `) }