From 891551f9dde7d9e468df51bfc11d873f68349e99 Mon Sep 17 00:00:00 2001 From: adman Date: Mon, 6 Apr 2026 00:34:28 +0200 Subject: [PATCH] simple api cli --- api/unauthenticated.go | 130 +++++++++++++++++++++++++++++++++++++++++ cmd/strom/api/main.go | 60 +++++++++++++++++++ cmd/strom/main.go | 4 +- 3 files changed, 193 insertions(+), 1 deletion(-) create mode 100644 api/unauthenticated.go create mode 100644 cmd/strom/api/main.go diff --git a/api/unauthenticated.go b/api/unauthenticated.go new file mode 100644 index 0000000..f307327 --- /dev/null +++ b/api/unauthenticated.go @@ -0,0 +1,130 @@ +package api + +import ( + "bytes" + "encoding/json" + "fmt" + "io" + "net/http" + "strings" +) + +func BlockedServers() (s []string, err error) { + resp, err := http.Get("https://sessionserver.mojang.com/blockedservers") + if err != nil { + return + } + defer resp.Body.Close() + d, err := io.ReadAll(resp.Body) + if err != nil { + return + } + s = strings.Split(string(d), "\n") + return +} + +type ProfileProperty struct { + Name string `json:"name"` + Signature string `json:"signature,omitempty"` + Value string `json:"value"` +} + +type ProfileResp struct { + Id string `json:"id"` + Name string `json:"name"` + Legacy bool `json:"legacy"` + Properties []ProfileProperty `json:"properties"` +} + +func HasJoined(name, server, ip string) (p ProfileResp, err error) { + url := "https://sessionserver.mojang.com/session/minecraft/hasJoined?username=" + name + "&serverId=" + server + if ip != "" { + url += "&ip=" + ip + } + resp, err := http.Get(url) + if err != nil { + return + } + defer resp.Body.Close() + if resp.StatusCode != 200 { + err = fmt.Errorf("bad status code %v", resp.StatusCode) + return + } + err = json.NewDecoder(resp.Body).Decode(&p) + return +} + +type MojangPublicKeysResp struct { + ProfilePropertyKeys []string `json:"profilePropertyKeys"` + PlayerCertificateKeys []string `json:"playerCertificateKeys"` + + // AuthenticationKeys are undocumented by wiki.vg but they exist as of the time of writing so ill include them + AuthenticationKeys []string `json:"authenticationKeys"` +} + +func MojangPublicKeys() (keys MojangPublicKeysResp, err error) { + resp, err := http.Get("https://sessionserver.mojang.com/session/minecraft/profile/public_key_by_token") + if err != nil { + return + } + defer resp.Body.Close() + err = json.NewDecoder(resp.Body).Decode(&keys) + return +} + +func Profile(id string, unsinged bool) (p ProfileResp, err error) { + url := "https://sessionserver.mojang.com/session/minecraft/profile/" + id + if !unsinged { + url += "?unsigned=false" + } + resp, err := http.Get(url) + if err != nil { + return + } + defer resp.Body.Close() + err = json.NewDecoder(resp.Body).Decode(&p) + return +} + +type IdName struct { + Id string `json:"id"` + Name string `json:"name"` +} + +func NameToId(name string) (idName IdName, err error) { + // may also use these: + //https://api.mojang.com/users/profiles/minecraft/ + //https://api.minecraftservices.com/minecraft/profile/lookup/name/ + //https://api.mojang.com/minecraft/profile/lookup/name/ + resp, err := http.Get("https://api.minecraftservices.com/minecraft/profile/lookup/name/" + name) + if err != nil { + return + } + defer resp.Body.Close() + err = json.NewDecoder(resp.Body).Decode(&idName) + return +} + +func NameToIdBulk(names []string) (idNames []IdName, err error) { + b := bytes.NewBuffer(nil) + err = json.NewEncoder(b).Encode(names) + if err != nil { + return + } + // may also use these: + //https://api.mojang.com/profiles/minecraft + //https://api.minecraftservices.com/minecraft/profile/lookup/bulk/byname + //https://api.mojang.com/minecraft/profile/lookup/bulk/byname + r, err := http.NewRequest("POST", "https://api.minecraftservices.com/minecraft/profile/lookup/bulk/byname", b) + if err != nil { + return + } + r.Header.Set("Content-Type", "application/json") + resp, err := http.DefaultClient.Do(r) + if err != nil { + return + } + defer resp.Body.Close() + err = json.NewDecoder(resp.Body).Decode(&idNames) + return +} diff --git a/cmd/strom/api/main.go b/cmd/strom/api/main.go new file mode 100644 index 0000000..a7fa2ec --- /dev/null +++ b/cmd/strom/api/main.go @@ -0,0 +1,60 @@ +package api + +import ( + "encoding/json" + "errors" + "flag" + "os" + + "github.com/admin-else/strom/api" +) + +var cmd = flag.NewFlagSet("api", flag.ContinueOnError) +var tokenFlag = cmd.String("token", os.Getenv("MC_TOKEN"), "token to use") + +var renameFlag = cmd.String("rename", "", "rename the player") + +var uuidFlag = cmd.String("uuid", "", "get data about player with uuid") +var nameFlag = cmd.String("name", "", "get data about player with name") +var unsignedFlag = cmd.Bool("unsigned", true, "use unsigned mode for properties signatures") + +var PleaseProvideArgumentErr = errors.New("please provide an argument") + +func Run(args []string) (err error) { + err = cmd.Parse(args) + if err != nil { + return + } + a := &api.Account{} + if *tokenFlag != "" { + a, err = api.NewAccountFromYGG(*tokenFlag) + if err != nil { + return + } + } + var data any + if *renameFlag != "" { + data, err = a.ChangeName(*renameFlag) + if err != nil { + return + } + } else if *uuidFlag != "" || *nameFlag != "" { + if *nameFlag != "" { + var idName api.IdName + idName, err = api.NameToId(*nameFlag) + if err != nil { + return + } + *uuidFlag = idName.Id + } + data, err = api.Profile(*uuidFlag, *unsignedFlag) + if err != nil { + return + } + } else { + err = PleaseProvideArgumentErr + return + } + err = json.NewEncoder(os.Stdout).Encode(data) + return +} diff --git a/cmd/strom/main.go b/cmd/strom/main.go index a4bc22e..e9629fb 100644 --- a/cmd/strom/main.go +++ b/cmd/strom/main.go @@ -8,6 +8,7 @@ import ( "os" "slices" + "github.com/admin-else/strom/cmd/strom/api" "github.com/admin-else/strom/cmd/strom/offline_uuid" "github.com/admin-else/strom/cmd/strom/packet_inspector" "github.com/admin-else/strom/cmd/strom/print_nbt" @@ -22,6 +23,7 @@ var subcommands = map[string]func(args []string) error{ "print-nbt": print_nbt.Run, "status": status.Run, "packet-spy": packet_inspector.Run, + "api": api.Run, } func Help(args []string) (err error) { @@ -37,7 +39,7 @@ func mainE() (err error) { // Debug stuff //slog.SetLogLoggerLevel(slog.LevelDebug) - //args = []string{"TEST", "status", "-srv", "-addr", "tcpshield.1f2d.net:25565"} + args = []string{"TEST", "api", "-name", "Admin_Else"} if len(args) < 2 { err = ExpectedASubcommandErr