├── .github └── workflows │ └── go.yml ├── .gitignore ├── LICENSE ├── README.md ├── client.go ├── const.go ├── errors.go ├── go.mod ├── go.sum ├── namehistory.go ├── profile.go └── status.go /.github/workflows/go.yml: -------------------------------------------------------------------------------- 1 | name: Go 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | 11 | build: 12 | name: Build 13 | runs-on: ubuntu-latest 14 | steps: 15 | 16 | - name: Set up Go 1.14 17 | uses: actions/setup-go@v1 18 | with: 19 | go-version: 1.14 20 | id: go 21 | 22 | - name: Check out code into the Go module directory 23 | uses: actions/checkout@v2 24 | 25 | - name: Get dependencies 26 | run: | 27 | go get -v -t -d ./... 28 | if [ -f Gopkg.toml ]; then 29 | curl https://raw.githubusercontent.com/golang/dep/master/install.sh | sh 30 | dep ensure 31 | fi 32 | 33 | - name: Build 34 | run: go build -v . 35 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | *.iml -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Lukas Schulte Pelkum 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![go.dev reference](https://img.shields.io/badge/go.dev-reference-007d9c?logo=go&logoColor=white)](https://pkg.go.dev/github.com/Lukaesebrot/mojango) 2 | ![Go](https://github.com/Lukaesebrot/mojango/workflows/Go/badge.svg) 3 | 4 | # mojango 5 | A modern and fast Golang wrapper around the Mojang API 6 | 7 | ## Installation 8 | Use the following command to download the wrapper: 9 | ``` 10 | go get -u github.com/Lukaesebrot/mojango 11 | ``` 12 | 13 | ## Usage 14 | Before you can use the wrapper, you need to create a client: 15 | ```go 16 | func main() { 17 | // Create a new mojango client 18 | client := mojango.New() 19 | } 20 | ``` 21 | 22 | Using this client, you can now use the wrapper's functions as you like: 23 | ```go 24 | func main() { 25 | // Create a new mojango client 26 | client := mojango.New() 27 | 28 | // Fetch a player's UUID 29 | uuid, err := client.FetchUUID("ksebrt"); if err != nil { 30 | panic(err) 31 | } 32 | fmt.Println(uuid) // Output: 39cc0f91869a486494160d610f18b993 33 | } 34 | ``` 35 | 36 | ## Contribution/Help 37 | If you found a bug, have any suggestions or want to improve some code, feel free to create an issue 38 | or pull request! 39 | -------------------------------------------------------------------------------- /client.go: -------------------------------------------------------------------------------- 1 | package mojango 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | 7 | "github.com/valyala/fasthttp" 8 | ) 9 | 10 | // Client represents an API client 11 | type Client struct { 12 | http *fasthttp.Client 13 | } 14 | 15 | // New creates a new fasthttp client and wraps it into an API client 16 | func New() *Client { 17 | return &Client{ 18 | http: &fasthttp.Client{ 19 | Name: "mojango", 20 | }, 21 | } 22 | } 23 | 24 | // FetchStatus fetches the states of all Mojang services and wraps them into a single object 25 | func (client *Client) FetchStatus() (*Status, error) { 26 | // Call the Mojang status endpoint 27 | code, body, err := client.http.Get(nil, fmt.Sprintf("%s/check", uriStatus)) 28 | if err != nil { 29 | return nil, err 30 | } 31 | 32 | // Handle possible errors 33 | if code != fasthttp.StatusOK { 34 | return nil, errorFromCode(code) 35 | } 36 | 37 | // Parse the result into a status object and return it 38 | return parseStatusFromBody(body) 39 | } 40 | 41 | // FetchUUID fetches the current UUID of the given username 42 | func (client *Client) FetchUUID(username string) (string, error) { 43 | return client.FetchUUIDAtTime(username, -1) 44 | } 45 | 46 | // FetchUUIDAtTime fetches the UUID of the given username at a given timestamp 47 | func (client *Client) FetchUUIDAtTime(username string, timestamp int64) (string, error) { 48 | // Call the Mojang profile endpoint 49 | atExtension := "" 50 | if timestamp >= 0 { 51 | atExtension = fmt.Sprintf("?at=%d", timestamp) 52 | } 53 | code, body, err := client.http.Get(nil, fmt.Sprintf("%s/users/profiles/minecraft/%s%s", uriApi, username, atExtension)) 54 | if err != nil { 55 | return "", err 56 | } 57 | 58 | // Handle possible errors 59 | if code != fasthttp.StatusOK { 60 | return "", errorFromCode(code) 61 | } 62 | 63 | // Parse the result into a map containing the profile data 64 | var result map[string]interface{} 65 | err = json.Unmarshal(body, &result) 66 | if err != nil { 67 | return "", err 68 | } 69 | 70 | // Return the UUID of the requested profile 71 | return result["id"].(string), nil 72 | } 73 | 74 | // FetchMultipleUUIDs fetches the UUIDs of the given usernames 75 | func (client *Client) FetchMultipleUUIDs(usernames []string) (map[string]string, error) { 76 | // Define the request object 77 | request := fasthttp.AcquireRequest() 78 | defer fasthttp.ReleaseRequest(request) 79 | request.SetRequestURI(fmt.Sprintf("%s/profiles/minecraft", uriApi)) 80 | request.Header.SetMethod("POST") 81 | request.Header.SetContentType("application/json") 82 | reqBody, err := json.Marshal(usernames) 83 | if err != nil { 84 | return nil, err 85 | } 86 | request.SetBody(reqBody) 87 | 88 | // Define the response object 89 | response := fasthttp.AcquireResponse() 90 | defer fasthttp.ReleaseResponse(response) 91 | 92 | // Call the Mojang profile endpoint 93 | err = client.http.Do(request, response) 94 | if err != nil { 95 | return nil, err 96 | } 97 | 98 | // Define the important response values 99 | code := response.StatusCode() 100 | body := response.Body() 101 | 102 | // Handle possible errors 103 | if code != fasthttp.StatusOK { 104 | return nil, errorFromCode(code) 105 | } 106 | 107 | // Parse the response body into a list of results 108 | var rawResults []struct { 109 | UUID string `json:"id"` 110 | Name string `json:"name"` 111 | } 112 | err = json.Unmarshal(body, &rawResults) 113 | if err != nil { 114 | return nil, err 115 | } 116 | 117 | // Parse the list of results into a map and return it 118 | result := make(map[string]string) 119 | for _, rawResult := range rawResults { 120 | result[rawResult.Name] = rawResult.UUID 121 | } 122 | return result, nil 123 | } 124 | 125 | // FetchNameHistory fetches all names of the given UUID and their corresponding changing timestamps 126 | func (client *Client) FetchNameHistory(uuid string) ([]NameHistoryEntry, error) { 127 | // Call the Mojang profile endpoint 128 | code, body, err := client.http.Get(nil, fmt.Sprintf("%s/user/profiles/%s/names", uriApi, uuid)) 129 | if err != nil { 130 | return nil, err 131 | } 132 | 133 | // Handle possible errors 134 | if code != fasthttp.StatusOK { 135 | return nil, errorFromCode(code) 136 | } 137 | 138 | // Parse the response body into a list of name history entries and return it 139 | var entries []NameHistoryEntry 140 | err = json.Unmarshal(body, &entries) 141 | if err != nil { 142 | return nil, err 143 | } 144 | return entries, nil 145 | } 146 | 147 | // FetchProfile fetches the profile of the given UUID 148 | func (client *Client) FetchProfile(uuid string, unsigned bool) (*Profile, error) { 149 | // Call the Mojang profile endpoint 150 | code, body, err := client.http.Get(nil, fmt.Sprintf("%s/session/minecraft/profile/%s?unsigned=%t", uriSession, uuid, unsigned)) 151 | if err != nil { 152 | return nil, err 153 | } 154 | 155 | // Handle possible errors 156 | if code != fasthttp.StatusOK { 157 | return nil, errorFromCode(code) 158 | } 159 | 160 | // Parse the response body into a profile and return it 161 | profile := new(Profile) 162 | err = json.Unmarshal(body, profile) 163 | if err != nil { 164 | return nil, err 165 | } 166 | return profile, nil 167 | } 168 | -------------------------------------------------------------------------------- /const.go: -------------------------------------------------------------------------------- 1 | package mojango 2 | 3 | const ( 4 | uriStatus = "https://status.mojang.com" 5 | uriApi = "https://api.mojang.com" 6 | uriSession = "https://sessionserver.mojang.com" 7 | ) 8 | -------------------------------------------------------------------------------- /errors.go: -------------------------------------------------------------------------------- 1 | package mojango 2 | 3 | import ( 4 | "errors" 5 | "github.com/valyala/fasthttp" 6 | "strconv" 7 | ) 8 | 9 | // Define possible known errors 10 | var ErrTooManyRequests = errors.New("too many Mojang API requests") 11 | var ErrNoContent = errors.New("no Mojang API result") 12 | 13 | // errorFromCode returns a corresponding error to the given status code 14 | func errorFromCode(statusCode int) error { 15 | switch statusCode { 16 | case fasthttp.StatusTooManyRequests: 17 | return ErrTooManyRequests 18 | case fasthttp.StatusNoContent: 19 | return ErrNoContent 20 | default: 21 | return errors.New(strconv.Itoa(statusCode)) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/Lukaesebrot/mojango 2 | 3 | go 1.14 4 | 5 | require github.com/valyala/fasthttp v1.9.0 6 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/klauspost/compress v1.8.2 h1:Bx0qjetmNjdFXASH02NSAREKpiaDwkO1DRZ3dV2KCcs= 2 | github.com/klauspost/compress v1.8.2/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= 3 | github.com/klauspost/cpuid v1.2.1 h1:vJi+O/nMdFt0vqm8NZBI6wzALWdA2X+egi0ogNyrC/w= 4 | github.com/klauspost/cpuid v1.2.1/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= 5 | github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= 6 | github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= 7 | github.com/valyala/fasthttp v1.9.0 h1:hNpmUdy/+ZXYpGy0OBfm7K0UQTzb73W0T0U4iJIVrMw= 8 | github.com/valyala/fasthttp v1.9.0/go.mod h1:FstJa9V+Pj9vQ7OJie2qMHdwemEDaDiSdBnvPM1Su9w= 9 | github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV9WzVtRmSR+PDvWpU/qWl4Wa5LApYYX4ZtKbio= 10 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 11 | golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 12 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 13 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 14 | -------------------------------------------------------------------------------- /namehistory.go: -------------------------------------------------------------------------------- 1 | package mojango 2 | 3 | // NameHistoryEntry represents an entry of the name history of an account 4 | type NameHistoryEntry struct { 5 | Name string `json:"name"` 6 | ChangedToAt int64 `json:"changedToAt"` 7 | } 8 | -------------------------------------------------------------------------------- /profile.go: -------------------------------------------------------------------------------- 1 | package mojango 2 | 3 | // Profile represents a whole player profile 4 | type Profile struct { 5 | UUID string `json:"id"` 6 | Name string `json:"name"` 7 | Properties []ProfileProperty `json:"properties"` 8 | } 9 | 10 | // ProfileProperty represents a property of a player profile 11 | type ProfileProperty struct { 12 | Name string `json:"name"` 13 | Value string `json:"value"` 14 | Signature string `json:"signature"` 15 | } 16 | -------------------------------------------------------------------------------- /status.go: -------------------------------------------------------------------------------- 1 | package mojango 2 | 3 | import "encoding/json" 4 | 5 | // These constants represent the possible states of the Mojang services 6 | const ( 7 | StatusGreen = "green" 8 | StatusYellow = "yellow" 9 | StatusRed = "red" 10 | ) 11 | 12 | // Status contains all states of the Mojang services 13 | type Status struct { 14 | MinecraftWebsite string 15 | MojangWebsite string 16 | Session string 17 | SessionServer string 18 | AuthServer string 19 | Account string 20 | Textures string 21 | API string 22 | } 23 | 24 | // parseStatusFromBody parses a status object from the response body of the API 25 | func parseStatusFromBody(body []byte) (*Status, error) { 26 | // Parse multiple single states out of the response body 27 | var rawStates []map[string]string 28 | 29 | err := json.Unmarshal(body, &rawStates) 30 | if err != nil { 31 | return nil, err 32 | } 33 | 34 | // Create the status object and put the corresponding values in it 35 | status := new(Status) 36 | for _, stateMap := range rawStates { 37 | for key, state := range stateMap { 38 | switch key { 39 | case "minecraft.net": 40 | status.MinecraftWebsite = state 41 | break 42 | case "mojang.com": 43 | status.MojangWebsite = state 44 | break 45 | case "session.minecraft.net": 46 | status.Session = state 47 | break 48 | case "sessionserver.mojang.com": 49 | status.SessionServer = state 50 | break 51 | case "authserver.mojang.com": 52 | status.AuthServer = state 53 | break 54 | case "account.mojang.com": 55 | status.Account = state 56 | break 57 | case "textures.minecraft.net": 58 | status.Textures = state 59 | break 60 | case "api.mojang.com": 61 | status.API = state 62 | break 63 | } 64 | } 65 | } 66 | 67 | // Return the status object 68 | return status, nil 69 | } 70 | --------------------------------------------------------------------------------