diff --git a/bot/mcbot.go b/bot/mcbot.go index a5c9c19..2e05d78 100644 --- a/bot/mcbot.go +++ b/bot/mcbot.go @@ -6,8 +6,6 @@ package bot import ( "fmt" - "time" - "github.com/Tnze/go-mc/net" pk "github.com/Tnze/go-mc/net/packet" ) @@ -15,71 +13,6 @@ import ( // ProtocolVersion , the protocol version number of minecraft net protocol const ProtocolVersion = 498 -// PingAndList check server status and list online player. -// Returns a JSON data with server status, and the delay. -// -// For more information for JSON format, see https://wiki.vg/Server_List_Ping#Response -func PingAndList(addr string, port int) ([]byte, time.Duration, error) { - conn, err := net.DialMC(fmt.Sprintf("%s:%d", addr, port)) - if err != nil { - return nil, 0, err - } - - //握手 - err = conn.WritePacket( - //Handshake Packet - pk.Marshal( - 0x00, //Handshake packet ID - pk.VarInt(ProtocolVersion), //Protocol version - pk.String(addr), //Server's address - pk.UnsignedShort(port), - pk.Byte(1), - )) - if err != nil { - return nil, 0, fmt.Errorf("bot: send handshake packect fail: %v", err) - } - - //LIST - //请求服务器状态 - err = conn.WritePacket(pk.Marshal(0)) - if err != nil { - return nil, 0, fmt.Errorf("bot: send list packect fail: %v", err) - } - - //服务器返回状态 - recv, err := conn.ReadPacket() - if err != nil { - return nil, 0, fmt.Errorf("bot: recv list packect fail: %v", err) - } - var s pk.String - err = recv.Scan(&s) - if err != nil { - return nil, 0, fmt.Errorf("bot: scan list packect fail: %v", err) - } - - //PING - startTime := time.Now() - err = conn.WritePacket(pk.Marshal(0x01, pk.Long(startTime.Unix()))) - if err != nil { - return nil, 0, fmt.Errorf("bot: send ping packect fail: %v", err) - } - - recv, err = conn.ReadPacket() - if err != nil { - return nil, 0, fmt.Errorf("bot: recv pong packect fail: %v", err) - } - var t pk.Long - err = recv.Scan(&t) - if err != nil { - return nil, 0, fmt.Errorf("bot: scan pong packect fail: %v", err) - } - if t != pk.Long(startTime.Unix()) { - return nil, 0, fmt.Errorf("bot: pong packect no match: %v", err) - } - - return []byte(s), time.Since(startTime), err -} - // JoinServer connect a Minecraft server for playing the game. func (c *Client) JoinServer(addr string, port int) (err error) { //Connect diff --git a/bot/pinglist.go b/bot/pinglist.go new file mode 100644 index 0000000..d0afd5b --- /dev/null +++ b/bot/pinglist.go @@ -0,0 +1,94 @@ +package bot + +import ( + "fmt" + "time" + + "github.com/Tnze/go-mc/net" + pk "github.com/Tnze/go-mc/net/packet" +) + +// PingAndList check server status and list online player. +// Returns a JSON data with server status, and the delay. +// +// For more information for JSON format, see https://wiki.vg/Server_List_Ping#Response +func PingAndList(addr string, port int) ([]byte, time.Duration, error) { + conn, err := net.DialMC(fmt.Sprintf("%s:%d", addr, port)) + if err != nil { + return nil, 0, fmt.Errorf("bot: dial fail: %v", err) + } + return pingAndList(addr, port, conn) +} + +// PingAndListTimeout PingAndLIstTimeout is the version of PingAndList with max request time. +func PingAndListTimeout(addr string, port int, timeout time.Duration) ([]byte, time.Duration, error) { + deadLine := time.Now().Add(timeout) + + conn, err := net.DialMCTimeout(fmt.Sprintf("%s:%d", addr, port), timeout) + if err != nil { + return nil, 0, fmt.Errorf("bot: dial fail: %v", err) + } + + err = conn.Socket.SetDeadline(deadLine) + if err != nil { + return nil, 0, fmt.Errorf("bot: set deadline fail: %v", err) + } + + return pingAndList(addr, port, conn) +} + +func pingAndList(addr string, port int, conn *net.Conn) ([]byte, time.Duration, error) { + //握手 + err := conn.WritePacket( + //Handshake Packet + pk.Marshal( + 0x00, //Handshake packet ID + pk.VarInt(ProtocolVersion), //Protocol version + pk.String(addr), //Server's address + pk.UnsignedShort(port), + pk.Byte(1), + )) + if err != nil { + return nil, 0, fmt.Errorf("bot: send handshake packect fail: %v", err) + } + + //LIST + //请求服务器状态 + err = conn.WritePacket(pk.Marshal(0)) + if err != nil { + return nil, 0, fmt.Errorf("bot: send list packect fail: %v", err) + } + + //服务器返回状态 + recv, err := conn.ReadPacket() + if err != nil { + return nil, 0, fmt.Errorf("bot: recv list packect fail: %v", err) + } + var s pk.String + err = recv.Scan(&s) + if err != nil { + return nil, 0, fmt.Errorf("bot: scan list packect fail: %v", err) + } + + //PING + startTime := time.Now() + err = conn.WritePacket(pk.Marshal(0x01, pk.Long(startTime.Unix()))) + if err != nil { + return nil, 0, fmt.Errorf("bot: send ping packect fail: %v", err) + } + + recv, err = conn.ReadPacket() + if err != nil { + return nil, 0, fmt.Errorf("bot: recv pong packect fail: %v", err) + } + var t pk.Long + err = recv.Scan(&t) + if err != nil { + return nil, 0, fmt.Errorf("bot: scan pong packect fail: %v", err) + } + if t != pk.Long(startTime.Unix()) { + return nil, 0, fmt.Errorf("bot: pong packect no match: %v", err) + } + + return []byte(s), time.Since(startTime), err +} diff --git a/go.mod b/go.mod index 5bf52ac..ef839b1 100644 --- a/go.mod +++ b/go.mod @@ -2,4 +2,7 @@ module github.com/Tnze/go-mc go 1.12 -require github.com/google/uuid v1.1.1 +require ( + github.com/google/uuid v1.1.1 + github.com/satori/go.uuid v1.2.0 +) diff --git a/go.sum b/go.sum index b864886..8139f77 100644 --- a/go.sum +++ b/go.sum @@ -1,2 +1,4 @@ github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww= +github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= diff --git a/net/conn.go b/net/conn.go index 9c60114..a8cbb4e 100644 --- a/net/conn.go +++ b/net/conn.go @@ -6,6 +6,7 @@ import ( "crypto/cipher" "io" "net" + "time" pk "github.com/Tnze/go-mc/net/packet" ) @@ -51,8 +52,18 @@ func DialMC(addr string) (*Conn, error) { }, err } +// DialMCTimeout acts like DialMC but takes a timeout. +func DialMCTimeout(addr string, timeout time.Duration) (*Conn, error) { + conn, err := net.DialTimeout("tcp", addr, timeout) + return &Conn{ + Socket: conn, + ByteReader: bufio.NewReader(conn), + Writer: conn, + }, err +} + // WrapConn warp an net.Conn to MC-Conn -// Helps you modify the connection process (eg. set timeout). +// Helps you modify the connection process (eg. using DialContext). func WrapConn(conn net.Conn) *Conn { return &Conn{ Socket: conn,