diff --git a/Makefile b/Makefile index 5fad57d..41393b5 100644 --- a/Makefile +++ b/Makefile @@ -24,5 +24,4 @@ clean: proto: protoc --go_out=/home/michael/go/src response.proto -.PHONY: run doc build debs update-debs fmt proto - +.PHONY: run doc build debs update-debs fmt proto \ No newline at end of file diff --git a/client.go b/client/client.go similarity index 54% rename from client.go rename to client/client.go index d9154a3..2141375 100644 --- a/client.go +++ b/client/client.go @@ -1,4 +1,4 @@ -package main +package client import ( "bytes" @@ -11,50 +11,48 @@ import ( "github.com/golang/protobuf/jsonpb" ) -type client struct { - cmd *cmd - conn net.Conn +// Client TCP client +type Client struct { + Addr string + Port int + Version uint64 + Conn net.Conn } -func newConn(cmd *cmd) (*client, error) { - conn, err := net.Dial("tcp", cmd.addr+":"+strconv.Itoa(cmd.port)) +// New client connection +func New(addr string, port int, ver uint64) (*Client, error) { + conn, err := net.Dial("tcp", addr+":"+strconv.Itoa(port)) if err != nil { return nil, err } - return &client{ - cmd: cmd, - conn: conn, + return &Client{ + Addr: addr, + Port: port, + Version: ver, + Conn: conn, }, nil } -func (client *client) write() error { - if err := client.conn.SetWriteDeadline(time.Now().Add(10 * time.Second)); err != nil { - client.conn.Close() +func (client *Client) write() error { + if err := client.Conn.SetWriteDeadline(time.Now().Add(10 * time.Second)); err != nil { return err } - client.conn.Write(handshake(client.cmd.addr, client.cmd.port, client.cmd.version)) - client.conn.Write([]byte{0x01, 0x00}) + client.Conn.Write(handshake(client.Addr, client.Port, client.Version)) return nil } -func (client *client) read() (*pb.Response, error) { +func (client *Client) read() (*pb.Response, error) { var response pb.Response - if err := client.conn.SetReadDeadline(time.Now().Add(10 * time.Second)); err != nil { - client.conn.Close() - return nil, err - } - recvBuf := make([]byte, 512) - n, err := client.conn.Read(recvBuf) + buf := make([]byte, 1024) + n, err := client.Conn.Read(buf) if err != nil { if netErr, ok := err.(net.Error); ok && netErr.Timeout() { - client.conn.Close() return nil, err } - client.conn.Close() return nil, err } - b := bytes.Split(recvBuf[:n], []byte("{")) - ne := bytes.SplitAfterN(recvBuf[:n], b[0], 2) + b := bytes.Split(buf[:n], []byte("{")) + ne := bytes.SplitAfterN(buf[:n], b[0], 2) trim := bytes.TrimSuffix(ne[1], []byte("\x00")) js := &jsonpb.Unmarshaler{AllowUnknownFields: true} if err := js.Unmarshal(bytes.NewBuffer(trim), &response); err != nil { @@ -63,15 +61,6 @@ func (client *client) read() (*pb.Response, error) { return &response, nil } -func (client *client) pingServer() time.Duration { - ping := make([]byte, 1) - start := time.Now() - client.conn.Write([]byte{0x01, 0x00}) - _, _ = client.conn.Read(ping[:]) - diff := time.Now().Sub(start) - return diff -} - func packetlength(b ...[]byte) (length int) { for _, bytes := range b { length += len(bytes) @@ -97,5 +86,6 @@ func handshake(addr string, port int, ver uint64) []byte { handshake.WriteString(addr) handshake.Write(p) handshake.Write(state) + handshake.Write([]byte{0x01, 0x00}) return handshake.Bytes() } diff --git a/client/status.go b/client/status.go new file mode 100644 index 0000000..f331ce9 --- /dev/null +++ b/client/status.go @@ -0,0 +1,31 @@ +package client + +import ( + "time" + + "git.0cd.xyz/michael/mcstatus/pb" +) + +// GetStatus gets minecraft server status +func (client *Client) GetStatus() (*pb.Response, error) { + for { + if err := client.write(); err != nil { + return nil, err + } + resp, err := client.read() + if err != nil { + return nil, err + } + return resp, nil + } +} + +// PingServer pings Minecraft server +func (client *Client) PingServer() time.Duration { + ping := make([]byte, 1) + start := time.Now() + client.Conn.Write([]byte{0x01, 0x00}) + _, _ = client.Conn.Read(ping[:]) + diff := time.Now().Sub(start) + return diff +} diff --git a/main.go b/main.go index 8d41e7d..21b3ad5 100644 --- a/main.go +++ b/main.go @@ -4,6 +4,8 @@ import ( "encoding/json" "fmt" "os" + + "git.0cd.xyz/michael/mcstatus/client" ) func main() { @@ -15,52 +17,40 @@ func main() { func run() error { cmd := ui() - conn, err := newConn(cmd) + client, err := client.New(cmd.addr, cmd.port, cmd.version) if err != nil { return err } + defer client.Conn.Close() for { - if err := conn.write(); err != nil { - return err - } - resp, err := conn.read() + status, err := client.GetStatus() if err != nil { return err } if cmd.ping { - fmt.Printf("Ping: %+v\n", conn.pingServer()) - if err = conn.conn.Close(); err != nil { - conn.conn.Close() - return err - } + fmt.Printf("Ping: %+v\n", client.PingServer()) return nil } if cmd.raw { - json, err := json.MarshalIndent(&resp, "", " ") + json, err := json.MarshalIndent(&status, "", " ") if err != nil { - conn.conn.Close() return err } fmt.Printf("%s\n", string(json)) - conn.conn.Close() - return err + return nil } 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 { + status.Description.Text, + status.Players.Online, + status.Players.Max, + status.Version.Name) + if status.Players.Online >= 1 { fmt.Println("Online:") - for _, player := range resp.Players.Sample { + for _, player := range status.Players.Sample { fmt.Printf("\t%s\n", player.Name) } } - fmt.Printf("Ping: %+v\n", conn.pingServer()) - if err = conn.conn.Close(); err != nil { - conn.conn.Close() - return err - } + fmt.Printf("Ping: %+v\n", client.PingServer()) return nil } } diff --git a/pb/response.pb.go b/pb/response.pb.go index 717b137..08baaef 100644 --- a/pb/response.pb.go +++ b/pb/response.pb.go @@ -1,13 +1,12 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.25.0-devel -// protoc v3.7.1 +// protoc v3.14.0 // source: response.proto package pb import ( - proto "github.com/golang/protobuf/proto" protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" reflect "reflect" @@ -21,10 +20,6 @@ const ( _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ) -// This is a compile-time assertion that a sufficiently up-to-date version -// of the legacy proto package is being used. -const _ = proto.ProtoPackageIsVersion4 - type Response struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -219,7 +214,8 @@ type Response_Description struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - Text string `protobuf:"bytes,1,opt,name=text,proto3" json:"text,omitempty"` + Extra []*Response_Description_Extra `protobuf:"bytes,1,rep,name=extra,proto3" json:"extra,omitempty"` + Text string `protobuf:"bytes,2,opt,name=text,proto3" json:"text,omitempty"` } func (x *Response_Description) Reset() { @@ -254,6 +250,13 @@ func (*Response_Description) Descriptor() ([]byte, []int) { return file_response_proto_rawDescGZIP(), []int{0, 2} } +func (x *Response_Description) GetExtra() []*Response_Description_Extra { + if x != nil { + return x.Extra + } + return nil +} + func (x *Response_Description) GetText() string { if x != nil { return x.Text @@ -316,11 +319,58 @@ func (x *Response_Players_Sample) GetId() string { return "" } +type Response_Description_Extra struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Text string `protobuf:"bytes,1,opt,name=text,proto3" json:"text,omitempty"` +} + +func (x *Response_Description_Extra) Reset() { + *x = Response_Description_Extra{} + if protoimpl.UnsafeEnabled { + mi := &file_response_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Response_Description_Extra) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Response_Description_Extra) ProtoMessage() {} + +func (x *Response_Description_Extra) ProtoReflect() protoreflect.Message { + mi := &file_response_proto_msgTypes[5] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Response_Description_Extra.ProtoReflect.Descriptor instead. +func (*Response_Description_Extra) Descriptor() ([]byte, []int) { + return file_response_proto_rawDescGZIP(), []int{0, 2, 0} +} + +func (x *Response_Description_Extra) GetText() string { + if x != nil { + return x.Text + } + return "" +} + var File_response_proto protoreflect.FileDescriptor var file_response_proto_rawDesc = []byte{ 0x0a, 0x0e, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x12, 0x02, 0x70, 0x62, 0x22, 0xb7, 0x03, 0x0a, 0x08, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x12, 0x02, 0x70, 0x62, 0x22, 0x8a, 0x04, 0x0a, 0x08, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2e, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x70, 0x62, 0x2e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, @@ -345,12 +395,17 @@ var file_response_proto_rawDesc = []byte{ 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x52, 0x06, 0x73, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x1a, 0x2c, 0x0a, 0x06, 0x53, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x69, - 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x1a, 0x21, 0x0a, 0x0b, 0x44, - 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x65, - 0x78, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x65, 0x78, 0x74, 0x42, 0x21, - 0x5a, 0x1f, 0x67, 0x69, 0x74, 0x2e, 0x30, 0x63, 0x64, 0x2e, 0x78, 0x79, 0x7a, 0x2f, 0x6d, 0x69, - 0x63, 0x68, 0x61, 0x65, 0x6c, 0x2f, 0x6d, 0x63, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x2f, 0x70, - 0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x1a, 0x74, 0x0a, 0x0b, 0x44, + 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x34, 0x0a, 0x05, 0x65, 0x78, + 0x74, 0x72, 0x61, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x70, 0x62, 0x2e, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, + 0x69, 0x6f, 0x6e, 0x2e, 0x45, 0x78, 0x74, 0x72, 0x61, 0x52, 0x05, 0x65, 0x78, 0x74, 0x72, 0x61, + 0x12, 0x12, 0x0a, 0x04, 0x74, 0x65, 0x78, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, + 0x74, 0x65, 0x78, 0x74, 0x1a, 0x1b, 0x0a, 0x05, 0x45, 0x78, 0x74, 0x72, 0x61, 0x12, 0x12, 0x0a, + 0x04, 0x74, 0x65, 0x78, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x65, 0x78, + 0x74, 0x42, 0x21, 0x5a, 0x1f, 0x67, 0x69, 0x74, 0x2e, 0x30, 0x63, 0x64, 0x2e, 0x78, 0x79, 0x7a, + 0x2f, 0x6d, 0x69, 0x63, 0x68, 0x61, 0x65, 0x6c, 0x2f, 0x6d, 0x63, 0x73, 0x74, 0x61, 0x74, 0x75, + 0x73, 0x2f, 0x70, 0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -365,24 +420,26 @@ func file_response_proto_rawDescGZIP() []byte { return file_response_proto_rawDescData } -var file_response_proto_msgTypes = make([]protoimpl.MessageInfo, 5) +var file_response_proto_msgTypes = make([]protoimpl.MessageInfo, 6) var file_response_proto_goTypes = []interface{}{ - (*Response)(nil), // 0: pb.Response - (*Response_Version)(nil), // 1: pb.Response.Version - (*Response_Players)(nil), // 2: pb.Response.Players - (*Response_Description)(nil), // 3: pb.Response.Description - (*Response_Players_Sample)(nil), // 4: pb.Response.Players.Sample + (*Response)(nil), // 0: pb.Response + (*Response_Version)(nil), // 1: pb.Response.Version + (*Response_Players)(nil), // 2: pb.Response.Players + (*Response_Description)(nil), // 3: pb.Response.Description + (*Response_Players_Sample)(nil), // 4: pb.Response.Players.Sample + (*Response_Description_Extra)(nil), // 5: pb.Response.Description.Extra } var file_response_proto_depIdxs = []int32{ 1, // 0: pb.Response.version:type_name -> pb.Response.Version 2, // 1: pb.Response.players:type_name -> pb.Response.Players 3, // 2: pb.Response.description:type_name -> pb.Response.Description 4, // 3: pb.Response.Players.sample:type_name -> pb.Response.Players.Sample - 4, // [4:4] is the sub-list for method output_type - 4, // [4:4] is the sub-list for method input_type - 4, // [4:4] is the sub-list for extension type_name - 4, // [4:4] is the sub-list for extension extendee - 0, // [0:4] is the sub-list for field type_name + 5, // 4: pb.Response.Description.extra:type_name -> pb.Response.Description.Extra + 5, // [5:5] is the sub-list for method output_type + 5, // [5:5] is the sub-list for method input_type + 5, // [5:5] is the sub-list for extension type_name + 5, // [5:5] is the sub-list for extension extendee + 0, // [0:5] is the sub-list for field type_name } func init() { file_response_proto_init() } @@ -451,6 +508,18 @@ func file_response_proto_init() { return nil } } + file_response_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Response_Description_Extra); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } } type x struct{} out := protoimpl.TypeBuilder{ @@ -458,7 +527,7 @@ func file_response_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_response_proto_rawDesc, NumEnums: 0, - NumMessages: 5, + NumMessages: 6, NumExtensions: 0, NumServices: 0, }, diff --git a/response.proto b/response.proto index a630fb3..7e4f869 100644 --- a/response.proto +++ b/response.proto @@ -21,7 +21,11 @@ message Response { } Players players = 2; message Description { - string text = 1; + message Extra { + string text = 1; + } + repeated Extra extra = 1; + string text = 2; } Description description = 3; string favicon = 4;