Skip to content

Commit

Permalink
add GetGameRankAndScore endpoint
Browse files Browse the repository at this point in the history
  • Loading branch information
joshraphael committed Nov 21, 2024
1 parent 0be495f commit 70e2bf4
Show file tree
Hide file tree
Showing 7 changed files with 219 additions and 5 deletions.
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,4 +73,5 @@ For convenience, the API docs and examples can be found in the tables below
|`GetGameExtended()`|Get extended metadata about a game.|[docs](https://api-docs.retroachievements.org/v1/get-game-extended.html) \| [example](examples/game/getgameextended/getgameextended.go)|
|`GetGameHashes()`|Get the hashes linked to a game.|[docs](https://api-docs.retroachievements.org/v1/get-game-hashes.html) \| [example](examples/game/getgamehashes/getgamehashes.go)|
|`GetAchievementCount()`|Get the list of achievement IDs for a game.|[docs](https://api-docs.retroachievements.org/v1/get-achievement-count.html) \| [example](examples/game/getachievementcount/getachievementcount.go)|
|`GetAchievementDistribution()`|Gets how many players have unlocked how many achievements for a game.|[docs](https://api-docs.retroachievements.org/v1/get-achievement-distribution.html) \| [example](examples/game/getachievementdistribution/getachievementdistribution.go)|
|`GetAchievementDistribution()`|Gets how many players have unlocked how many achievements for a game.|[docs](https://api-docs.retroachievements.org/v1/get-achievement-distribution.html) \| [example](examples/game/getachievementdistribution/getachievementdistribution.go)|
|`GetGameRankAndScore()`|Gets metadata about either the latest masters for a game, or the highest points earners for a game.|[docs](https://api-docs.retroachievements.org/v1/get-game-rank-and-score.html) \| [example](examples/game/getgamerankandscore/getgamerankandscore.go)|
28 changes: 28 additions & 0 deletions examples/game/getgamerankandscore/getgamerankandscore.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// Package getgamerankandscore provides an example for getting metadata about either the latest masters for a game, or the highest points earners for a game.
package main

import (
"fmt"
"os"

"github.com/joshraphael/go-retroachievements"
"github.com/joshraphael/go-retroachievements/models"
)

/*
Test script, add RA_API_KEY to your env and use `go run getgamerankandscore.go`
*/
func main() {
secret := os.Getenv("RA_API_KEY")

client := retroachievements.NewClient(secret)

resp, err := client.GetGameRankAndScore(models.GetGameRankAndScoreParameters{
GameID: 1,
})
if err != nil {
panic(err)
}

fmt.Printf("%+v\n", resp)
}
26 changes: 26 additions & 0 deletions game.go
Original file line number Diff line number Diff line change
Expand Up @@ -112,3 +112,29 @@ func (c *Client) GetAchievementDistribution(params models.GetAchievementDistribu
}
return resp, nil
}

// GetGameRankAndScore gets metadata about either the latest masters for a game, or the highest points earners for a game.
func (c *Client) GetGameRankAndScore(params models.GetGameRankAndScoreParameters) ([]models.GetGameRankAndScore, error) {
details := []raHttp.RequestDetail{
raHttp.Method(http.MethodGet),
raHttp.Path("/API/API_GetGameRankAndScore.php"),
raHttp.APIToken(c.Secret),
raHttp.Game(params.GameID),
}
if params.LatestMasters != nil {
if *params.LatestMasters {
details = append(details, raHttp.To(1))
} else {
details = append(details, raHttp.To(0))
}
}
r, err := c.do(details...)
if err != nil {
return nil, fmt.Errorf("calling endpoint: %w", err)
}
resp, err := raHttp.ResponseList[models.GetGameRankAndScore](r)
if err != nil {
return nil, fmt.Errorf("parsing response list: %w", err)
}
return resp, nil
}
143 changes: 139 additions & 4 deletions game_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,7 @@ func TestGetGameExtended(tt *testing.T) {
responseMessage models.GetGameExtented
responseError models.ErrorResponse
response func(messageBytes []byte, errorBytes []byte) []byte
assert func(t *testing.T, game *models.GetGameExtented, err error)
assert func(t *testing.T, resp *models.GetGameExtented, err error)
}{
{
name: "fail to call endpoint",
Expand Down Expand Up @@ -418,7 +418,7 @@ func TestGetGameHashes(tt *testing.T) {
responseMessage models.GetGameHashes
responseError models.ErrorResponse
response func(messageBytes []byte, errorBytes []byte) []byte
assert func(t *testing.T, game *models.GetGameHashes, err error)
assert func(t *testing.T, resp *models.GetGameHashes, err error)
}{
{
name: "fail to call endpoint",
Expand Down Expand Up @@ -538,7 +538,7 @@ func TestGetAchievementCount(tt *testing.T) {
responseMessage models.GetAchievementCount
responseError models.ErrorResponse
response func(messageBytes []byte, errorBytes []byte) []byte
assert func(t *testing.T, game *models.GetAchievementCount, err error)
assert func(t *testing.T, resp *models.GetAchievementCount, err error)
}{
{
name: "fail to call endpoint",
Expand Down Expand Up @@ -677,7 +677,7 @@ func TestGetAchievementDistribution(tt *testing.T) {
responseMessage models.GetAchievementDistribution
responseError models.ErrorResponse
response func(messageBytes []byte, errorBytes []byte) []byte
assert func(t *testing.T, game *models.GetAchievementDistribution, err error)
assert func(t *testing.T, resp *models.GetAchievementDistribution, err error)
}{
{
name: "fail to call endpoint",
Expand Down Expand Up @@ -835,3 +835,138 @@ func TestGetAchievementDistribution(tt *testing.T) {
})
}
}

func TestGetGameRankAndScore(tt *testing.T) {
latest := true
all := false
lastAwarded1, err := time.Parse(time.DateTime, "2022-01-28 21:18:15")
require.NoError(tt, err)
lastAwarded2, err := time.Parse(time.DateTime, "2022-01-29 04:19:34")
require.NoError(tt, err)
tests := []struct {
name string
params models.GetGameRankAndScoreParameters
modifyURL func(url string) string
responseCode int
responseMessage []models.GetGameRankAndScore
responseError models.ErrorResponse
response func(messageBytes []byte, errorBytes []byte) []byte
assert func(t *testing.T, resp []models.GetGameRankAndScore, err error)
}{
{
name: "fail to call endpoint",
params: models.GetGameRankAndScoreParameters{
GameID: 14402,
LatestMasters: &latest,
},
modifyURL: func(url string) string {
return ""
},
responseCode: http.StatusOK,
response: func(messageBytes []byte, errorBytes []byte) []byte {
return messageBytes
},
assert: func(t *testing.T, resp []models.GetGameRankAndScore, err error) {
require.Nil(t, resp)
require.EqualError(t, err, "calling endpoint: Get \"/API/API_GetGameRankAndScore.php?g=14402&t=1&y=some_secret\": unsupported protocol scheme \"\"")
},
},
{
name: "error response",
params: models.GetGameRankAndScoreParameters{
GameID: 14402,
LatestMasters: &all,
},
modifyURL: func(url string) string {
return url
},
responseCode: http.StatusUnauthorized,
responseError: models.ErrorResponse{
Message: "test",
Errors: []models.ErrorDetail{
{
Status: http.StatusUnauthorized,
Code: "unauthorized",
Title: "Not Authorized",
},
},
},
response: func(messageBytes []byte, errorBytes []byte) []byte {
return errorBytes
},
assert: func(t *testing.T, resp []models.GetGameRankAndScore, err error) {
require.Nil(t, resp)
require.EqualError(t, err, "parsing response list: error responses: [401] Not Authorized")
},
},
{
name: "success",
params: models.GetGameRankAndScoreParameters{
GameID: 515,
LatestMasters: &latest,
},
modifyURL: func(url string) string {
return url
},
responseCode: http.StatusOK,
responseMessage: []models.GetGameRankAndScore{
{
User: "Blazekickn",
NumAchievements: 61,
TotalScore: 453,
LastAward: models.DateTime{
Time: lastAwarded1,
},
},
{
User: "mamekin",
NumAchievements: 61,
TotalScore: 453,
LastAward: models.DateTime{
Time: lastAwarded2,
},
},
},
response: func(messageBytes []byte, errorBytes []byte) []byte {
return messageBytes
},
assert: func(t *testing.T, resp []models.GetGameRankAndScore, err error) {
require.NotNil(t, resp)
require.Len(t, resp, 2)
require.Equal(t, "Blazekickn", resp[0].User)
require.Equal(t, 61, resp[0].NumAchievements)
require.Equal(t, 453, resp[0].TotalScore)
require.Equal(t, lastAwarded1, resp[0].LastAward.Time)
require.Equal(t, "mamekin", resp[1].User)
require.Equal(t, 61, resp[1].NumAchievements)
require.Equal(t, 453, resp[1].TotalScore)
require.Equal(t, lastAwarded2, resp[1].LastAward.Time)
require.NoError(t, err)
},
},
}
for _, test := range tests {
tt.Run(test.name, func(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
expectedPath := "/API/API_GetGameRankAndScore.php"
if r.URL.Path != expectedPath {
t.Errorf("Expected to request '%s', got: %s", expectedPath, r.URL.Path)
}
w.WriteHeader(test.responseCode)
messageBytes, err := json.Marshal(test.responseMessage)
require.NoError(t, err)
errBytes, err := json.Marshal(test.responseError)
require.NoError(t, err)
resp := test.response(messageBytes, errBytes)
num, err := w.Write(resp)
require.NoError(t, err)
require.Equal(t, num, len(resp))
}))
defer server.Close()

client := retroachievements.New(test.modifyURL(server.URL), "some_secret")
resp, err := client.GetGameRankAndScore(test.params)
test.assert(t, resp, err)
})
}
}
7 changes: 7 additions & 0 deletions http/request.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,13 @@ func BearerToken(token string) RequestDetail {
})
}

// UserAgent adds an User-Agent header with the package version
func UserAgent() RequestDetail {
return requestDetailFn(func(r *Request) {
r.Headers["User-Agent"] = "go-retroachievements/v0.0.0"
})
}

// Username adds the username to the query parameters
func Username(username string) RequestDetail {
return requestDetailFn(func(r *Request) {
Expand Down
2 changes: 2 additions & 0 deletions http/request_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ func TestNewRequest(t *testing.T) {
"http://localhost",
raHttp.Path("/api/v1/some_resource"),
raHttp.Method(http.MethodPost),
raHttp.UserAgent(),
raHttp.APIToken("secret_token"),
raHttp.BearerToken("secret_bearer"),
raHttp.Username("myUsername"),
Expand All @@ -38,6 +39,7 @@ func TestNewRequest(t *testing.T) {
Host: "http://localhost",
Headers: map[string]string{
"Authorization": "Bearer secret_bearer",
"User-Agent": "go-retroachievements/v0.0.0",
},
Params: map[string]string{
"y": "secret_token",
Expand Down
15 changes: 15 additions & 0 deletions models/game.go
Original file line number Diff line number Diff line change
Expand Up @@ -130,3 +130,18 @@ type GetAchievementDistributionParameters struct {
}

type GetAchievementDistribution map[string]int

type GetGameRankAndScoreParameters struct {
// The target game ID
GameID int

// [Optional] Return the latest masters (dafualt: false)
LatestMasters *bool
}

type GetGameRankAndScore struct {
User string `json:"User"`
NumAchievements int `json:"NumAchievements"`
TotalScore int `json:"TotalScore"`
LastAward DateTime `json:"LastAward"`
}

0 comments on commit 70e2bf4

Please sign in to comment.