Skip to content

Commit

Permalink
add GetRecentGameAwards endpoints
Browse files Browse the repository at this point in the history
  • Loading branch information
joshraphael committed Nov 23, 2024
1 parent 92c8288 commit aa05227
Show file tree
Hide file tree
Showing 7 changed files with 315 additions and 1 deletion.
8 changes: 7 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,4 +100,10 @@ For convenience, the API docs and examples can be found in the tables below

|Function|Description|Links|
|-|-|-|
|`GetComments()`|Gets comments of a specified kind: game, achievement, or user.|[docs](https://api-docs.retroachievements.org/v1/get-comments.html) \| [example](examples/comment/getcomments/getcomments.go)|
|`GetComments()`|Gets comments of a specified kind: game, achievement, or user.|[docs](https://api-docs.retroachievements.org/v1/get-comments.html) \| [example](examples/comment/getcomments/getcomments.go)|

<h3>Feed</h3>

|Function|Description|Links|
|-|-|-|
|`GetRecentGameAwards()`|Gets all recently granted game awards across the site's userbase.|[docs](https://api-docs.retroachievements.org/v1/get-recent-game-awards.html) \| [example](examples/feed/getrecentgameawards/getrecentgameawards.go)|
26 changes: 26 additions & 0 deletions examples/feed/getrecentgameawards/getrecentgameawards.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// Package getrecentgameawards provides an example for getting all recently granted game awards across the site's userbase.
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 getrecentgameawards.go`
*/
func main() {
secret := os.Getenv("RA_API_KEY")

client := retroachievements.NewClient(secret)

resp, err := client.GetRecentGameAwards(models.GetRecentGameAwardsParameters{})
if err != nil {
panic(err)
}

fmt.Printf("%+v\n", resp)
}
58 changes: 58 additions & 0 deletions feed.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package retroachievements

import (
"fmt"
"net/http"

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

// GetRecentGameAwards gets all recently granted game awards across the site's userbase.
func (c *Client) GetRecentGameAwards(params models.GetRecentGameAwardsParameters) (*models.GetRecentGameAwards, error) {
details := []raHttp.RequestDetail{
raHttp.Method(http.MethodGet),
raHttp.Path("/API/API_GetRecentGameAwards.php"),
raHttp.APIToken(c.Secret),
}
if params.StartingDate != nil {
details = append(details, raHttp.D(*params.StartingDate))
}
if params.Count != nil {
details = append(details, raHttp.C(*params.Count))
}
if params.Offset != nil {
details = append(details, raHttp.O(*params.Offset))
}
if params.IncludePartialAwards != nil {
beatenSoftcore := params.IncludePartialAwards.BeatenSoftcore
beatenHardcore := params.IncludePartialAwards.BeatenHardcore
completed := params.IncludePartialAwards.Completed
mastered := params.IncludePartialAwards.Mastered
if beatenSoftcore || beatenHardcore || completed || mastered {
k := []string{}
if beatenSoftcore {
k = append(k, "beaten-softcore")
}
if beatenHardcore {
k = append(k, "beaten-hardcore")
}
if beatenHardcore {
k = append(k, "completed")
}
if beatenHardcore {
k = append(k, "mastered")
}
details = append(details, raHttp.K(k))
}
}
r, err := c.do(details...)
if err != nil {
return nil, fmt.Errorf("calling endpoint: %w", err)
}
resp, err := raHttp.ResponseObject[models.GetRecentGameAwards](r)
if err != nil {
return nil, fmt.Errorf("parsing response object: %w", err)
}
return resp, nil
}
168 changes: 168 additions & 0 deletions feed_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
package retroachievements_test

import (
"encoding/json"
"net/http"
"net/http/httptest"
"testing"
"time"

"github.com/joshraphael/go-retroachievements"
"github.com/joshraphael/go-retroachievements/models"
"github.com/stretchr/testify/require"
)

func TestGetRecentGameAwards(tt *testing.T) {
count := 10
offset := 10
startingDate, err := time.Parse(models.RFC3339NumColonTZFormat, "2024-10-05T18:30:59+00:00")
require.NoError(tt, err)
awardDate, err := time.Parse(models.RFC3339NumColonTZFormat, "2024-11-23T13:39:21+00:00")
require.NoError(tt, err)
tests := []struct {
name string
params models.GetRecentGameAwardsParameters
modifyURL func(url string) string
responseCode int
responseMessage models.GetRecentGameAwards
responseError models.ErrorResponse
response func(messageBytes []byte, errorBytes []byte) []byte
assert func(t *testing.T, resp *models.GetRecentGameAwards, err error)
}{
{
name: "fail to call endpoint",
params: models.GetRecentGameAwardsParameters{
StartingDate: &startingDate,
Count: &count,
Offset: &offset,
IncludePartialAwards: &models.GetRecentGameAwardsParametersPartialAwards{
BeatenSoftcore: true,
BeatenHardcore: true,
Completed: true,
Mastered: true,
},
},
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.GetRecentGameAwards, err error) {
require.Nil(t, resp)
require.EqualError(t, err, "calling endpoint: Get \"/API/API_GetRecentGameAwards.php?c=10&d=2024-10-05&k=beaten-softcore%2Cbeaten-hardcore%2Ccompleted%2Cmastered&o=10&y=some_secret\": unsupported protocol scheme \"\"")
},
},
{
name: "error response",
params: models.GetRecentGameAwardsParameters{
StartingDate: &startingDate,
Count: &count,
Offset: &offset,
IncludePartialAwards: &models.GetRecentGameAwardsParametersPartialAwards{
BeatenSoftcore: true,
BeatenHardcore: true,
Completed: true,
Mastered: true,
},
},
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.GetRecentGameAwards, err error) {
require.Nil(t, resp)
require.EqualError(t, err, "parsing response object: error code 401 returned: {\"message\":\"test\",\"errors\":[{\"status\":401,\"code\":\"unauthorized\",\"title\":\"Not Authorized\"}]}")
},
},
{
name: "success",
params: models.GetRecentGameAwardsParameters{
StartingDate: &startingDate,
Count: &count,
Offset: &offset,
IncludePartialAwards: &models.GetRecentGameAwardsParametersPartialAwards{
BeatenSoftcore: true,
BeatenHardcore: true,
Completed: true,
Mastered: true,
},
},
modifyURL: func(url string) string {
return url
},
responseCode: http.StatusOK,
responseMessage: models.GetRecentGameAwards{
Total: 2,
Count: 1,
Results: []models.GetRecentGameAwardsResult{
{
User: "spoony",
AwardKind: "mastered",
AwardDate: models.RFC3339NumColonTZ{
Time: awardDate,
},
GameID: 7317,
GameTitle: "~Hack~ Pokémon Brown Version",
ConsoleID: 4,
ConsoleName: "Game Boy",
},
},
},
response: func(messageBytes []byte, errorBytes []byte) []byte {
return messageBytes
},
assert: func(t *testing.T, resp *models.GetRecentGameAwards, err error) {
require.NotNil(t, resp)
require.Equal(t, 2, resp.Total)
require.Equal(t, 1, resp.Count)
require.Len(t, resp.Results, 1)
require.Equal(t, "spoony", resp.Results[0].User)
require.Equal(t, "mastered", resp.Results[0].AwardKind)
require.Equal(t, awardDate, resp.Results[0].AwardDate.Time)
require.Equal(t, 7317, resp.Results[0].GameID)
require.Equal(t, "~Hack~ Pokémon Brown Version", resp.Results[0].GameTitle)
require.Equal(t, 4, resp.Results[0].ConsoleID)
require.Equal(t, "Game Boy", resp.Results[0].ConsoleName)
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_GetRecentGameAwards.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), "go-retroachievements/v0.0.0", "some_secret")
resp, err := client.GetRecentGameAwards(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 @@ -102,6 +102,13 @@ func I(i []string) RequestDetail {
})
}

// K adds a 'k' string list to the query parameters
func K(k []string) RequestDetail {
return requestDetailFn(func(r *Request) {
r.Params["k"] = strings.Join(k, ",")
})
}

// G adds a 'g' number to the query parameters
func G(g int) 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 @@ -27,6 +27,7 @@ func TestNewRequest(t *testing.T) {
raHttp.T(int(later.Unix())),
raHttp.D(now),
raHttp.I([]string{strconv.Itoa(2837), strconv.Itoa(4535)}),
raHttp.K([]string{"test1", "test2"}),
raHttp.G(345),
raHttp.A(1),
raHttp.C(20),
Expand All @@ -50,6 +51,7 @@ func TestNewRequest(t *testing.T) {
"t": "1709401023",
"d": "2024-03-02",
"i": "2837,4535",
"k": "test1,test2",
"g": "345",
"a": "1",
"c": "20",
Expand Down
47 changes: 47 additions & 0 deletions models/feed.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package models

import "time"

type GetRecentGameAwardsParameters struct {
// [Optional] Starting date (YYYY-MM-DD) (default: now).
StartingDate *time.Time

// [Optional] The number of records to return (default: 100, max: 500).
Count *int

// [Optional] The number of entries to skip (default: 0).
Offset *int

// [Optional] Return partial list of awards based on type (default: return everything).
IncludePartialAwards *GetRecentGameAwardsParametersPartialAwards
}

type GetRecentGameAwardsParametersPartialAwards struct {
// Include beaten softcore awards
BeatenSoftcore bool

// Include beaten hardcore awards
BeatenHardcore bool

// Include completed game awards
Completed bool

// Include mastered game awards
Mastered bool
}

type GetRecentGameAwards struct {
Count int `json:"Count"`
Total int `json:"Total"`
Results []GetRecentGameAwardsResult `json:"Results"`
}

type GetRecentGameAwardsResult struct {
User string `json:"User"`
AwardKind string `json:"AwardKind"`
AwardDate RFC3339NumColonTZ `json:"AwardDate"`
GameID int `json:"GameID"`
GameTitle string `json:"GameTitle"`
ConsoleID int `json:"ConsoleID"`
ConsoleName string `json:"ConsoleName"`
}

0 comments on commit aa05227

Please sign in to comment.