Skip to content

Commit

Permalink
add GetAchievementUnlocks endpoint
Browse files Browse the repository at this point in the history
  • Loading branch information
joshraphael committed Nov 22, 2024
1 parent 707e8e7 commit fcc6efc
Show file tree
Hide file tree
Showing 6 changed files with 309 additions and 3 deletions.
8 changes: 7 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,4 +88,10 @@ For convenience, the API docs and examples can be found in the tables below
|Function|Description|Links|
|-|-|-|
|`GetConsoleIDs()`|Gets the complete list of all system ID and name pairs on the site.|[docs](https://api-docs.retroachievements.org/v1/get-console-ids.html) \| [example](examples/system/getconsoleids/getconsoleids.go)|
|`GetGameList()`|Gets the complete list of games for a specified console on the site.|[docs](https://api-docs.retroachievements.org/v1/get-game-list.html) \| [example](examples/system/getgamelist/getgamelist.go)|
|`GetGameList()`|Gets the complete list of games for a specified console on the site.|[docs](https://api-docs.retroachievements.org/v1/get-game-list.html) \| [example](examples/system/getgamelist/getgamelist.go)|

<h3>Achievement</h3>

|Function|Description|Links|
|-|-|-|
|`GetAchievementUnlocks()`|Gets a list of users who have earned an achievement.|[docs](https://api-docs.retroachievements.org/v1/get-achievement-unlocks.html) \| [example](examples/achievement/getachievementunlocks/getachievementunlocks.go)|
34 changes: 34 additions & 0 deletions achievement.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package retroachievements

import (
"fmt"
"net/http"

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

// GetAchievementUnlocks gets a list of users who have earned an achievement.
func (c *Client) GetAchievementUnlocks(params models.GetAchievementUnlocksParameters) (*models.GetAchievementUnlocks, error) {
details := []raHttp.RequestDetail{
raHttp.Method(http.MethodGet),
raHttp.Path("/API/API_GetAchievementUnlocks.php"),
raHttp.APIToken(c.Secret),
raHttp.A(params.AchievementID),
}
if params.Count != nil {
details = append(details, raHttp.C(*params.Count))
}
if params.Offset != nil {
details = append(details, raHttp.O(*params.Offset))
}
r, err := c.do(details...)
if err != nil {
return nil, fmt.Errorf("calling endpoint: %w", err)
}
resp, err := raHttp.ResponseObject[models.GetAchievementUnlocks](r)
if err != nil {
return nil, fmt.Errorf("parsing response object: %w", err)
}
return resp, nil
}
186 changes: 186 additions & 0 deletions achievement_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
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 TestGetAchievementUnlocks(tt *testing.T) {
count := 10
offset := 10
achievementType := "progression"
dateCreated, err := time.Parse(time.DateTime, "2012-11-02 00:03:12")
require.NoError(tt, err)
dateModified, err := time.Parse(time.DateTime, "2023-09-30 02:00:49")
require.NoError(tt, err)
dateAwarded, err := time.Parse(time.RFC3339Nano, "2024-11-22T17:25:17.000000Z")
require.NoError(tt, err)
tests := []struct {
name string
params models.GetAchievementUnlocksParameters
modifyURL func(url string) string
responseCode int
responseMessage models.GetAchievementUnlocks
responseError models.ErrorResponse
response func(messageBytes []byte, errorBytes []byte) []byte
assert func(t *testing.T, resp *models.GetAchievementUnlocks, err error)
}{
{
name: "fail to call endpoint",
params: models.GetAchievementUnlocksParameters{
AchievementID: 14402,
Count: &count,
Offset: &offset,
},
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.GetAchievementUnlocks, err error) {
require.Nil(t, resp)
require.EqualError(t, err, "calling endpoint: Get \"/API/API_GetAchievementUnlocks.php?a=14402&c=10&o=10&y=some_secret\": unsupported protocol scheme \"\"")
},
},
{
name: "error response",
params: models.GetAchievementUnlocksParameters{
AchievementID: 14402,
Count: &count,
Offset: &offset,
},
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.GetAchievementUnlocks, err error) {
require.Nil(t, resp)
require.EqualError(t, err, "parsing response object: error responses: [401] Not Authorized")
},
},
{
name: "success",
params: models.GetAchievementUnlocksParameters{
AchievementID: 14402,
Count: &count,
Offset: &offset,
},
modifyURL: func(url string) string {
return url
},
responseCode: http.StatusOK,
responseMessage: models.GetAchievementUnlocks{
UnlocksCount: 15906,
UnlocksHardcoreCount: 8223,
TotalPlayers: 38104,
Achievement: models.GetAchievementUnlocksAchievement{
ID: 1,
Title: "Ring Collector",
Description: "Collect 100 rings",
Points: 5,
TrueRatio: 7,
Author: "Scott",
DateCreated: models.DateTime{
Time: dateCreated,
},
DateModified: models.DateTime{
Time: dateModified,
},
Type: &achievementType,
},
Console: models.GetAchievementUnlocksConsole{
ID: 1,
Title: "Genesis/Mega Drive",
},
Game: models.GetAchievementUnlocksGame{
ID: 1,
Title: "Sonic the Hedgehog",
},
Unlocks: []models.GetAchievementUnlocksUnlock{
{
User: "redjedia",
RAPoints: 524,
RASoftcorePoints: 1615,
DateAwarded: dateAwarded,
HardcoreMode: 0,
},
},
},
response: func(messageBytes []byte, errorBytes []byte) []byte {
return messageBytes
},
assert: func(t *testing.T, resp *models.GetAchievementUnlocks, err error) {
require.NotNil(t, resp)
require.Equal(t, 15906, resp.UnlocksCount)
require.Equal(t, 8223, resp.UnlocksHardcoreCount)
require.Equal(t, 38104, resp.TotalPlayers)
require.Equal(t, 1, resp.Achievement.ID)
require.Equal(t, "Ring Collector", resp.Achievement.Title)
require.Equal(t, "Collect 100 rings", resp.Achievement.Description)
require.Equal(t, 5, resp.Achievement.Points)
require.Equal(t, 7, resp.Achievement.TrueRatio)
require.Equal(t, "Scott", resp.Achievement.Author)
require.Equal(t, dateCreated, resp.Achievement.DateCreated.Time)
require.Equal(t, dateModified, resp.Achievement.DateModified.Time)
require.NotNil(t, resp.Achievement.Type)
require.Equal(t, achievementType, *resp.Achievement.Type)
require.Equal(t, 1, resp.Console.ID)
require.Equal(t, "Genesis/Mega Drive", resp.Console.Title)
require.Equal(t, 1, resp.Game.ID)
require.Equal(t, "Sonic the Hedgehog", resp.Game.Title)
require.Len(t, resp.Unlocks, 1)
require.Equal(t, "redjedia", resp.Unlocks[0].User)
require.Equal(t, 524, resp.Unlocks[0].RAPoints)
require.Equal(t, 1615, resp.Unlocks[0].RASoftcorePoints)
require.Equal(t, dateAwarded, resp.Unlocks[0].DateAwarded)
require.Equal(t, 0, resp.Unlocks[0].HardcoreMode)
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_GetAchievementUnlocks.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.GetAchievementUnlocks(test.params)
test.assert(t, resp, err)
})
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// Package getachievementunlocks provides an example for getting a list of users who have earned an achievement
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 getachievementunlocks.go`
*/
func main() {
secret := os.Getenv("RA_API_KEY")

client := retroachievements.NewClient(secret)

resp, err := client.GetAchievementUnlocks(models.GetAchievementUnlocksParameters{
AchievementID: 1,
})
if err != nil {
panic(err)
}

fmt.Printf("%+v\n", resp)
}
2 changes: 0 additions & 2 deletions leaderboards_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,6 @@ func TestGetGameLeaderboards(tt *testing.T) {
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.GetGameLeaderboards(test.params)
test.assert(t, resp, err)
Expand Down Expand Up @@ -265,7 +264,6 @@ func TestGetLeaderboardEntries(tt *testing.T) {
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.GetLeaderboardEntries(test.params)
test.assert(t, resp, err)
Expand Down
54 changes: 54 additions & 0 deletions models/achievement.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package models

import "time"

type GetAchievementUnlocksParameters struct {
// The target achievement ID
AchievementID int

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

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

type GetAchievementUnlocks struct {
Achievement GetAchievementUnlocksAchievement `json:"Achievement"`
Console GetAchievementUnlocksConsole `json:"Console"`
Game GetAchievementUnlocksGame `json:"Game"`
UnlocksCount int `json:"UnlocksCount"`
UnlocksHardcoreCount int `json:"UnlocksHardcoreCount"`
TotalPlayers int `json:"TotalPlayers"`
Unlocks []GetAchievementUnlocksUnlock `json:"Unlocks"`
}

type GetAchievementUnlocksAchievement struct {
ID int `json:"ID"`
Title string `json:"Title"`
Description string `json:"Description"`
Points int `json:"Points"`
TrueRatio int `json:"TrueRatio"`
Author string `json:"Author"`
DateCreated DateTime `json:"DateCreated"`
DateModified DateTime `json:"DateModified"`
Type *string `json:"Type"`
}

type GetAchievementUnlocksConsole struct {
ID int `json:"ID"`
Title string `json:"Title"`
}

type GetAchievementUnlocksGame struct {
ID int `json:"ID"`
Title string `json:"Title"`
}

type GetAchievementUnlocksUnlock struct {
User string `json:"User"`
RAPoints int `json:"RAPoints"`
RASoftcorePoints int `json:"RASoftcorePoints"`
DateAwarded time.Time `json:"DateAwarded"`
HardcoreMode int `json:"HardcoreMode"`
}

0 comments on commit fcc6efc

Please sign in to comment.