commit 20edf2a7d5430ec3ec472016c674896b361e3721 Author: Michael Date: Tue Sep 1 22:46:44 2020 +0100 first commit Signed-off-by: Michael diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..fc79b7b --- /dev/null +++ b/LICENSE @@ -0,0 +1,19 @@ +MIT License Copyright (c) 2020 Michael Lindman + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice (including the next +paragraph) shall be included in all copies or substantial portions of the +Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS +OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF +OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..d74c356 --- /dev/null +++ b/README.md @@ -0,0 +1,39 @@ +# mcstatus + +Simple golang app to get the current online status of Minecraft servers + +## Usage + +from a terminal session run `mcstatus` with the address and port arguemnts for the server: + +```sh +./mcstatus -addr 127.0.0.1 -port 25565 +Name: A Minecraft Server +Players: 0/20 +Version: 1.16.2 +Ping: 123.698µs +``` + +## Licesne + +```text +MIT License Copyright (c) 2020 Michael Lindman + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice (including the next +paragraph) shall be included in all copies or substantial portions of the +Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS +OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF +OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +``` diff --git a/main.go b/main.go new file mode 100644 index 0000000..db30a16 --- /dev/null +++ b/main.go @@ -0,0 +1,135 @@ +package main + +import ( + "bytes" + "encoding/binary" + "encoding/json" + "flag" + "fmt" + "log" + "net" + "strconv" + "time" +) + +func packetlength(b ...[]byte) (length int) { + for _, bytes := range b { + length += len(bytes) + } + return length +} + +func handshake(addr string, port int, ver uint64) []byte { + id := []byte{0x00} + state := []byte{0x01} + + version := make([]byte, 2) + binary.PutUvarint(version, ver) + p := make([]byte, 2) + binary.BigEndian.PutUint16(p, uint16(port)) + length := packetlength(id, version, []byte(addr), []byte(p), state) + 1 + + var handshake bytes.Buffer + handshake.WriteByte(byte(length)) + handshake.Write(id) + handshake.Write(version) + handshake.WriteByte(byte(len(addr))) + handshake.WriteString(addr) + handshake.Write(p) + handshake.Write(state) + return handshake.Bytes() +} + +func main() { + addr := flag.String("addr", "127.0.0.1", "Server address") + port := flag.Int("port", 25565, "Server Port") + ver := flag.Uint64("ver", 751, "Minecraft protocol version number") + flag.Parse() + + conn, err := net.Dial("tcp", *addr+":"+strconv.Itoa(*port)) + if err != nil { + log.Fatal(err) + } + + for { + err := conn.SetWriteDeadline(time.Now().Add(30 * time.Second)) + if err != nil { + log.Println("WriteDealine failed:", err) + return + } + + conn.Write(handshake(*addr, *port, *ver)) + conn.Write([]byte{0x01, 0x00}) + + recvBuf := make([]byte, 512) + var resp response + + err = conn.SetReadDeadline(time.Now().Add(10 * time.Second)) + if err != nil { + log.Println("SetReadDealine failed:", err) + return + } + + n, err := conn.Read(recvBuf) + if err != nil { + if netErr, ok := err.(net.Error); ok && netErr.Timeout() { + log.Println("read timeout:", err) + } else { + log.Println("read error", err) + } + } + + requestright := recvBuf[:n] + b := bytes.Split(requestright, []byte("{")) + ne := bytes.SplitAfterN(requestright, b[0], 2) + after := bytes.TrimSuffix(ne[1], []byte("\x00")) + + if err := json.Unmarshal(after, &resp); err != nil { + log.Println(err) + conn.Close() + return + } + fmt.Printf("Name: %s\nPlayers: %d/%d\nVersion: %s\n", + resp.Description.Text, + resp.Players.Online, + resp.Players.Max, + resp.Version.Name) + if resp.Players.Online >= 1 { + fmt.Println("Online:") + for _, player := range resp.Players.Sample { + fmt.Printf("\t%s\n", player.Name) + } + } + + recvBuf = make([]byte, 1) + start := time.Now() + conn.Write([]byte{0x01, 0x00}) + _, _ = conn.Read(recvBuf[:]) + diff := time.Now().Sub(start) + fmt.Printf("Ping: %+v\n", diff) + if err = conn.Close(); err != nil { + log.Println(err) + return + } + return + } +} + +type response struct { + Version struct { + Name string `json:"name"` + Protocol int `json:"protocol"` + } `json:"version"` + Players struct { + Max int `json:"max"` + Online int `json:"online"` + Sample []struct { + Name string `json:"name"` + ID string `json:"id"` + } `json:"sample"` + } `json:"players"` + Description struct { + Text string `json:"text"` + } `json:"description"` + Favicon string `json:"favicon"` +}