package main import ( "encoding/json" "flag" "fmt" "os" "strconv" "strings" "time" "github.com/VexoraDevelopment/mcstatus" "github.com/VexoraDevelopment/mcstatus/text" ) func main() { cfg, target, err := parseCLI(os.Args[1:]) if err != nil { fatal(err) } if target == "" { printUsage() os.Exit(2) } host, port, err := parseTarget(target) if err != nil { fatal(err) } client := mcstatus.NewAll(cfg.timeout) info, err := connect(client, cfg.edition, host, port, cfg.useQuery) if err != nil { fatal(err) } if cfg.asJSON { enc := json.NewEncoder(os.Stdout) enc.SetIndent("", " ") if err := enc.Encode(info); err != nil { fatal(err) } return } printText(info, cfg.pretty, cfg.motdRaw) } type cliConfig struct { edition string timeout time.Duration asJSON bool useQuery bool pretty bool motdRaw bool } func parseCLI(args []string) (cliConfig, string, error) { cfg := cliConfig{ edition: "auto", timeout: 3 * time.Second, } fs := flag.NewFlagSet("mcstatus", flag.ContinueOnError) fs.SetOutput(os.Stderr) edition := fs.String("edition", "auto", "server edition: auto, java, bedrock") timeout := fs.Duration("timeout", 3*time.Second, "request timeout") asJSON := fs.Bool("json", false, "print JSON output") useQuery := fs.Bool("query", false, "use full query when supported") pretty := fs.Bool("pretty", false, "use compact human-friendly output") motdRaw := fs.Bool("motd-raw", false, "print raw MOTD without cleanup") if len(args) > 0 { switch strings.ToLower(strings.TrimSpace(args[0])) { case "auto", "java", "bedrock": cfg.edition = strings.ToLower(strings.TrimSpace(args[0])) args = args[1:] } } if err := fs.Parse(args); err != nil { return cfg, "", err } cfg.edition = strings.ToLower(strings.TrimSpace(*edition)) if cfg.edition == "auto" && len(args) > 0 { // Preserve subcommand semantics while still allowing -edition overrides. switch strings.ToLower(strings.TrimSpace(os.Args[1])) { case "auto", "java", "bedrock": cfg.edition = strings.ToLower(strings.TrimSpace(os.Args[1])) } } cfg.timeout = *timeout cfg.asJSON = *asJSON cfg.useQuery = *useQuery cfg.pretty = *pretty cfg.motdRaw = *motdRaw if fs.NArg() != 1 { return cfg, "", nil } return cfg, fs.Arg(0), nil } func printUsage() { fmt.Fprintf(os.Stderr, "Usage: %s [auto|java|bedrock] [flags] \n\n", os.Args[0]) fmt.Fprintln(os.Stderr, "Examples:") fmt.Fprintf(os.Stderr, " %s mc.hypixel.net\n", os.Args[0]) fmt.Fprintf(os.Stderr, " %s java mc.hypixel.net\n", os.Args[0]) fmt.Fprintf(os.Stderr, " %s bedrock -query play.example.com:19132\n\n", os.Args[0]) fmt.Fprintln(os.Stderr, "Flags:") fmt.Fprintln(os.Stderr, " -edition string") fmt.Fprintln(os.Stderr, " server edition: auto, java, bedrock") fmt.Fprintln(os.Stderr, " -timeout duration") fmt.Fprintln(os.Stderr, " request timeout (default 3s)") fmt.Fprintln(os.Stderr, " -json") fmt.Fprintln(os.Stderr, " print JSON output") fmt.Fprintln(os.Stderr, " -query") fmt.Fprintln(os.Stderr, " use full query when supported") fmt.Fprintln(os.Stderr, " -pretty") fmt.Fprintln(os.Stderr, " use compact human-friendly output") fmt.Fprintln(os.Stderr, " -motd-raw") fmt.Fprintln(os.Stderr, " print raw MOTD without cleanup") } func connect(client *mcstatus.Client, edition, host string, port int, useQuery bool) (*mcstatus.ServerInfo, error) { switch edition { case "auto": if info, err := connectJava(client, host, port, useQuery); err == nil { return info, nil } return connectBedrock(client, host, port, useQuery) case "java": return connectJava(client, host, port, useQuery) case "bedrock": return connectBedrock(client, host, port, useQuery) default: return nil, fmt.Errorf("unsupported edition %q", edition) } } func connectJava(client *mcstatus.Client, host string, port int, useQuery bool) (*mcstatus.ServerInfo, error) { if useQuery { return client.Java.QueryFull(host, port) } return client.Java.Connect(host, port) } func connectBedrock(client *mcstatus.Client, host string, port int, useQuery bool) (*mcstatus.ServerInfo, error) { if useQuery { return client.Bedrock.QueryFull(host, port) } return client.Bedrock.Connect(host, port) } func parseTarget(target string) (string, int, error) { host := strings.TrimSpace(target) if host == "" { return "", 0, fmt.Errorf("empty target") } if strings.Count(host, ":") == 1 && !strings.HasSuffix(host, "]") { parts := strings.SplitN(host, ":", 2) p, err := strconv.Atoi(parts[1]) if err != nil { return "", 0, fmt.Errorf("invalid port %q", parts[1]) } return parts[0], p, nil } if strings.HasPrefix(host, "[") { end := strings.LastIndex(host, "]") if end == -1 { return "", 0, fmt.Errorf("invalid IPv6 target %q", target) } ipv6Host := host[1:end] if len(host) == end+1 { return ipv6Host, 0, nil } if !strings.HasPrefix(host[end+1:], ":") { return "", 0, fmt.Errorf("invalid IPv6 target %q", target) } p, err := strconv.Atoi(host[end+2:]) if err != nil { return "", 0, fmt.Errorf("invalid port %q", host[end+2:]) } return ipv6Host, p, nil } return host, 0, nil } func printText(info *mcstatus.ServerInfo, pretty bool, motdRaw bool) { motd := info.MOTD if !motdRaw { motd = text.CleanMOTD(motd) } if pretty { printPretty(info, motd) return } fmt.Printf("Edition: %s\n", info.Edition) fmt.Printf("Address: %s:%d\n", info.Address, info.Port) if info.IP != "" { fmt.Printf("IP: %s\n", info.IP) } if info.Version != "" { fmt.Printf("Version: %s\n", info.Version) } if motd != "" { fmt.Printf("MOTD: %s\n", motd) } fmt.Printf("Players: %d/%d\n", info.Online, info.Slots) if info.GameMode != "" { fmt.Printf("GameMode: %s\n", info.GameMode) } if info.Map != "" { fmt.Printf("Map: %s\n", info.Map) } if info.Software != "" { fmt.Printf("Software: %s\n", info.Software) } if len(info.Plugins) > 0 { fmt.Printf("Plugins: %s\n", strings.Join(info.Plugins, ", ")) } if len(info.Players) > 0 { fmt.Printf("Sample Players: %s\n", strings.Join(info.Players, ", ")) } if info.Latency > 0 { fmt.Printf("Latency: %s\n", info.Latency) } fmt.Printf("Query: %t\n", info.Query) } func printPretty(info *mcstatus.ServerInfo, motd string) { fmt.Printf("%s %s:%d\n", strings.ToUpper(string(info.Edition)), info.Address, info.Port) if motd != "" { fmt.Printf("%s\n", motd) } fmt.Printf("Players %d/%d", info.Online, info.Slots) if info.Version != "" { fmt.Printf(" | Version %s", info.Version) } if info.Software != "" { fmt.Printf(" | %s", info.Software) } if info.Latency > 0 { fmt.Printf(" | %s", info.Latency) } fmt.Println() } func fatal(err error) { fmt.Fprintln(os.Stderr, "mcstatus:", err) os.Exit(1) }