From ad5dfeffd712a54f6d29a9cc015295049fb60355 Mon Sep 17 00:00:00 2001 From: Tnze Date: Fri, 9 Aug 2019 23:50:53 +0800 Subject: [PATCH] realms support --- .idea/dictionaries/cjd00.xml | 2 + README.md | 3 +- realms/invite.go | 13 ++++ realms/mco.go | 36 +++++++++ realms/realms.go | 88 ++++++++++++++++++++++ realms/realms_test.go | 36 +++++++++ realms/server.go | 139 +++++++++++++++++++++++++++++++++++ 7 files changed, 316 insertions(+), 1 deletion(-) create mode 100644 realms/invite.go create mode 100644 realms/mco.go create mode 100644 realms/realms.go create mode 100644 realms/realms_test.go create mode 100644 realms/server.go diff --git a/.idea/dictionaries/cjd00.xml b/.idea/dictionaries/cjd00.xml index c07b8a8..382d1c8 100644 --- a/.idea/dictionaries/cjd00.xml +++ b/.idea/dictionaries/cjd00.xml @@ -1,11 +1,13 @@ + astk cooldown craftable ender minecraft mojang + motd rcon teleport diff --git a/README.md b/README.md index b759c58..df2337f 100644 --- a/README.md +++ b/README.md @@ -10,10 +10,11 @@ There's some library in Go support you to create your Minecraft client or server - [x] Chat - [x] Parse NBT - [x] Simple MC robot lib -- [x] Mojang authenticate +- [x] Yggdrasil - [x] Minecraft network protocol - [x] RCON protocol - [x] Saves decoding /encoding +- [ ] Realms Server bot: - [x] Swing arm diff --git a/realms/invite.go b/realms/invite.go new file mode 100644 index 0000000..48fe2b0 --- /dev/null +++ b/realms/invite.go @@ -0,0 +1,13 @@ +package realms + +import "fmt" + +// Invite invite player to Realm +func (r *Realms) Invite(s Server, name, uuid string) error { + pl := struct { + Name string `json:"name"` + UUID string `json:"uuid"` + }{Name: name, UUID: uuid} + + return r.post(fmt.Sprintf("/invites/%d", s.ID), pl, struct{}{}) +} diff --git a/realms/mco.go b/realms/mco.go new file mode 100644 index 0000000..29f15ae --- /dev/null +++ b/realms/mco.go @@ -0,0 +1,36 @@ +package realms + +import "io/ioutil" + +// Available returns whether the user can access the Minecraft Realms service +func (r *Realms) Available() (ok bool, err error) { + err = r.get("/mco/available", &ok) + return +} + +// Compatible returns whether the clients version is up to date with Realms. +// if the client is outdated, it returns OUTDATED, +// if the client is running a snapshot, it returns OTHER, +// else it returns COMPATIBLE. +func (r *Realms) Compatible() (string, error) { + resp, err := r.c.Get(Domain + "/mco/client/compatible") + if err != nil { + return "", err + } + defer resp.Body.Close() + + rp, err := ioutil.ReadAll(resp.Body) + + return string(rp), err +} + +// TOS is what to join Realms servers you must agree to. +// Call this function will set this flag. +func (r *Realms) TOS()error{ + resp,err:=r.c.Post(Domain+"/mco/tos/agreed","application/json",nil) + if err != nil { + return err + } + defer resp.Body.Close() + return nil +} \ No newline at end of file diff --git a/realms/realms.go b/realms/realms.go new file mode 100644 index 0000000..55fe16a --- /dev/null +++ b/realms/realms.go @@ -0,0 +1,88 @@ +package realms + +import ( + "bytes" + "encoding/json" + "fmt" + "net/http" + "net/http/cookiejar" + "net/url" +) + +type Realms struct { + c http.Client +} + + +type Error struct { + ErrorCode int + ErrorMsg string +} + +func (e *Error) Error() string { + return fmt.Sprintf("[%d] %s", e.ErrorCode, e.ErrorMsg) +} + +// Domain is the URL of Realms API server +// Panic if it cannot be parse by url.Parse(). +var Domain = "https://pc.realms.minecraft.net" + +// New create a new Realms c with version, username, accessToken and UUID without dashes. +func New(version, user, astk, uuid string) *Realms { + r := &Realms{ + c: http.Client{}, + } + + var err error + r.c.Jar, err = cookiejar.New(nil) + if err != nil { + panic(err) + } + + d, err := url.Parse(Domain) + if err != nil { + panic("cannot parse realms.Domain as url: " + err.Error()) + } + + r.c.Jar.SetCookies(d, []*http.Cookie{ + {Name: "user", Value: user}, + {Name: "version", Value: version}, + {Name: "sid", Value: "token:" + astk + ":" + uuid}, + }) + + return r +} + +func (r *Realms) get(endpoint string, resp interface{}) error { + rawResp, err := r.c.Get(Domain + endpoint) + if err != nil { + return err + } + defer rawResp.Body.Close() + + err = json.NewDecoder(rawResp.Body).Decode(resp) + if err != nil { + return err + } + + return nil +} + +func (r *Realms) post(endpoint string, payload, resp interface{}) error { + data, err := json.Marshal(payload) + if err != nil { + return err + } + + rawResp, err := r.c.Post(Domain+endpoint, "application/json", bytes.NewReader(data)) + if err != nil { + return err + } + + err = json.NewDecoder(rawResp.Body).Decode(resp) + if err != nil { + return err + } + + return nil +} diff --git a/realms/realms_test.go b/realms/realms_test.go new file mode 100644 index 0000000..2641701 --- /dev/null +++ b/realms/realms_test.go @@ -0,0 +1,36 @@ +package realms + +import ( + "fmt" + "time" +) + +func ExampleRealms() { + var r *Realms + + r = New( + "1.14.4", + "Name", + "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", + "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", + ) + fmt.Println(r.Available()) + fmt.Println(r.Compatible()) + + servers, err := r.Worlds() + if err != nil { + panic(err) + } + + for _, v := range servers { + fmt.Println(v.Name, v.ID) + } + + time.Sleep(time.Second * 5) + if err := r.TOS(); err != nil { + panic(err) + } + + time.Sleep(time.Second * 5) + fmt.Println(r.Address(servers[0])) +} diff --git a/realms/server.go b/realms/server.go new file mode 100644 index 0000000..c6b7a94 --- /dev/null +++ b/realms/server.go @@ -0,0 +1,139 @@ +package realms + +import ( + "errors" + "fmt" +) + +type Server struct { + ID int + RemoteSubscriptionID string + Owner string + OwnerUUID string + Name string + MOTD string + State string + DaysLeft int + Expired bool + ExpiredTrial bool + WorldType string + Players []string + MaxPlayers int + MiniGameName *string + MiniGameID *int + MinigameImage *string + ActiveSlot int + //Slots interface{} + Member bool +} + +// Worlds return a list of servers that the user is invited to or owns. +func (r *Realms) Worlds() ([]Server, error) { + var resp struct { + Servers []Server + *Error + } + + err := r.get("/worlds", &resp) + if err != nil { + return nil, err + } + + if resp.Error != nil { + err = resp.Error + } + + return resp.Servers, err +} + +// Server returns a single server listing about a server. +// you must be the owner of the server. +func (r *Realms) Server(ID int) (s Server, err error) { + var resp = struct { + *Server + *Error + }{Server: &s} + + err = r.get(fmt.Sprintf("/worlds/%d", ID), &resp) + if err != nil { + return + } + + if resp.Error != nil { + err = resp.Error + } + + return +} + +// Address used to get the IP address for a server. +// Call TOS before you call this function. +func (r *Realms) Address(s Server) (string, error) { + var resp struct { + Address string + PendingUpdate bool + + ResourcePackUrl *string + ResourcePackHash *string + + *Error + } + + err := r.get(fmt.Sprintf("/worlds/v1/%d/join/pc", s.ID), &resp) + if err != nil { + return "", err + } + + if resp.Error != nil { + err = resp.Error + return "", err + } + + if resp.PendingUpdate { + return "", errors.New("pending update") + } + return resp.Address, err +} + +// Backups returns a list of backups for the world. +func (r *Realms) Backups(s Server) ([]int, error) { + var bs []int + err := r.get(fmt.Sprintf("/worlds/%d/backups", s.ID), &bs) + + return bs, err +} + +//func (r *Realms) Download() (link, resURL, resHash string) { +// var resp struct { +// DownloadLink string +// ResourcePackURL *string +// ResourcePackHash *string +// *Error +// } +// TODO: What's the $WORLD(1-4) means? +// err := r.get(fmt.Sprintf("/worlds/$ID/slot/$WORLD(1-4)/download", s.ID), &resp) +// +//} + +// Ops returns a list of operators for this server. +// You must own this server to view this. +func (r *Realms) Ops(s Server) (ops []string, err error) { + err = r.get(fmt.Sprintf("/ops/%d", s.ID), &ops) + return +} + +// SubscriptionLife returns the current life of a server subscription. +func (r *Realms) SubscriptionLife(s Server) (startDate int64, daysLeft int, Type string, err error) { + var resp = struct { + StartDate *int64 + DaysLeft *int + SubscriptionType *string + }{ + StartDate: &startDate, + DaysLeft: &daysLeft, + SubscriptionType: &Type, + } + + err = r.get(fmt.Sprintf("/subscriptions/%d", s.ID), &resp) + return +}