Skip to content

Commit

Permalink
Add GetGameInfoAndUserProgress call
Browse files Browse the repository at this point in the history
  • Loading branch information
joshraphael committed Aug 17, 2024
1 parent 7cb5259 commit f5f011f
Show file tree
Hide file tree
Showing 17 changed files with 412 additions and 23 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ For convenience, the API docs and examples can be found in the tables below
|`GetUserRecentAchievements(string,int)`|Get a list of achievements recently earned by the user.|[docs](https://api-docs.retroachievements.org/v1/get-user-recent-achievements.html) \| [example](examples/user/getuserrecentachievements/getuserrecentachievements.go)|
|`GetAchievementsEarnedBetween(string,Time,Time)`|Get a list of achievements earned by a user between two dates.|[docs](https://api-docs.retroachievements.org/v1/get-achievements-earned-between.html) \| [example](examples/user/getachievementsearnedbetween/getachievementsearnedbetween.go)|
|`GetAchievementsEarnedOnDay(string,Time)`|Get a list of achievements earned by a user on a given date.|[docs](https://api-docs.retroachievements.org/v1/get-achievements-earned-on-day.html) \| [example](examples/user/getachievementsearnedonday/getachievementsearnedonday.go)|
|`GetGameInfoAndUserProgress(string,int,bool)`|Get metadata about a game as well as a user's progress on that game.|[docs](https://api-docs.retroachievements.org/v1/get-game-info-and-user-progress.html) \| [example](examples/user/getgameinfoanduserprogress/getgameinfoanduserprogress.go)|

<h3>Game</h3>

Expand Down
2 changes: 1 addition & 1 deletion examples/game/getgame/getgame.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import (
)

/*
Test script for getting user profile. Add RA_API_KEY to your env and use `go run getgame.go`
Test script, add RA_API_KEY to your env and use `go run getgame.go`
*/
func main() {
secret := os.Getenv("RA_API_KEY")
Expand Down
2 changes: 1 addition & 1 deletion examples/game/getgameextended/getgameextended.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import (
)

/*
Test script for getting user profile. Add RA_API_KEY to your env and use `go run getgameextended.go`
Test script, add RA_API_KEY to your env and use `go run getgameextended.go`
*/
func main() {
secret := os.Getenv("RA_API_KEY")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import (
)

/*
Test script for getting user profile. Add RA_API_KEY to your env and use `go run getachievementsearnedbetween.go`
Test script, add RA_API_KEY to your env and use `go run getachievementsearnedbetween.go`
*/
func main() {
secret := os.Getenv("RA_API_KEY")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import (
)

/*
Test script for getting user profile. Add RA_API_KEY to your env and use `go run getachievementsearnedonday.go`
Test script, add RA_API_KEY to your env and use `go run getachievementsearnedonday.go`
*/
func main() {
secret := os.Getenv("RA_API_KEY")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// Package getgameinfoanduserprogress provides an example for a users achievements in the last X minutes
package main

import (
"fmt"
"os"

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

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

client := retroachievements.NewClient(secret)

resp, err := client.GetGameInfoAndUserProgress("joshraphael", 515, true)
if err != nil {
panic(err)
}

fmt.Printf("%+v\n", resp)
}
2 changes: 1 addition & 1 deletion examples/user/getuserprofile/getuserprofile.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import (
)

/*
Test script for getting user profile. Add RA_API_KEY to your env and use `go run getuserprofile.go`
Test script, add RA_API_KEY to your env and use `go run getuserprofile.go`
*/
func main() {
secret := os.Getenv("RA_API_KEY")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import (
)

/*
Test script for getting user profile. Add RA_API_KEY to your env and use `go run getuserrecentachievements.go`
Test script, add RA_API_KEY to your env and use `go run getuserrecentachievements.go`
*/
func main() {
secret := os.Getenv("RA_API_KEY")
Expand Down
6 changes: 3 additions & 3 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) {
ID: 2991,
IsFinal: 0,
RichPresencePatch: "e7a5e12072a6c976a1146756726fdd8c",
Updated: updated,
Updated: &updated,
ConsoleName: "PlayStation 2",
NumDistinctPlayers: 1287,
NumAchievements: 93,
Expand Down Expand Up @@ -263,7 +263,7 @@ func TestGetGameExtended(tt *testing.T) {
ID: 2991,
IsFinal: 0,
RichPresencePatch: "e7a5e12072a6c976a1146756726fdd8c",
Updated: updated,
Updated: &updated,
ConsoleName: "PlayStation 2",
NumDistinctPlayers: 1287,
NumAchievements: 93,
Expand Down Expand Up @@ -313,7 +313,7 @@ func TestGetGameExtended(tt *testing.T) {
require.Equal(t, 2991, extendedGameInfo.ID)
require.Equal(t, 0, extendedGameInfo.IsFinal)
require.Equal(t, "e7a5e12072a6c976a1146756726fdd8c", extendedGameInfo.RichPresencePatch)
require.Equal(t, updated, extendedGameInfo.Updated)
require.Equal(t, updated, *extendedGameInfo.Updated)
require.Equal(t, "PlayStation 2", extendedGameInfo.ConsoleName)
require.Equal(t, 1287, extendedGameInfo.NumDistinctPlayers)
require.Equal(t, 93, extendedGameInfo.NumAchievements)
Expand Down
18 changes: 18 additions & 0 deletions http/request.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,24 @@ func ID(id int) RequestDetail {
})
}

// GameID adds a target game id to the query parameters
func GameID(gameId int) RequestDetail {
return requestDetailFn(func(r *Request) {
r.Params["g"] = strconv.Itoa(gameId)
})
}

// AwardMetadata adds a target game id to the query parameters
func AwardMetadata(awardMetadata bool) RequestDetail {
return requestDetailFn(func(r *Request) {
a := "0"
if awardMetadata {
a = "1"
}
r.Params["a"] = a
})
}

// Path adds a URL path to the host
func Path(path string) RequestDetail {
return requestDetailFn(func(r *Request) {
Expand Down
4 changes: 4 additions & 0 deletions http/request_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ func TestNewRequest(t *testing.T) {
raHttp.ToTime(later),
raHttp.Date(now),
raHttp.ID(2837),
raHttp.GameID(345),
raHttp.AwardMetadata(true),
)

expected := &raHttp.Request{
Expand All @@ -42,6 +44,8 @@ func TestNewRequest(t *testing.T) {
"t": "1709401023",
"d": "2024-03-02",
"i": "2837",
"g": "345",
"a": "1",
},
}

Expand Down
20 changes: 11 additions & 9 deletions models/achievements.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,15 +40,17 @@ type UnlockedAchievement struct {
// GameAchievement is a representation of an achievement in a game
type GameAchievement struct {
Achievement
ID int `json:"ID"`
NumAwarded int `json:"NumAwarded"`
NumAwardedHardcore int `json:"NumAwardedHardcore"`
DateModified DateTime `json:"DateModified"`
DateCreated DateTime `json:"DateCreated"`
BadgeName string `json:"BadgeName"`
DisplayOrder int `json:"DisplayOrder"`
MemAddr string `json:"MemAddr"`
Type string `json:"type"`
ID int `json:"ID"`
NumAwarded int `json:"NumAwarded"`
NumAwardedHardcore int `json:"NumAwardedHardcore"`
DateModified DateTime `json:"DateModified"`
DateCreated DateTime `json:"DateCreated"`
BadgeName string `json:"BadgeName"`
DisplayOrder int `json:"DisplayOrder"`
MemAddr string `json:"MemAddr"`
Type string `json:"type"`
DateEarnedHardcore *DateTime `json:"DateEarnedHardcore"`
DateEarned *DateTime `json:"DateEarned"`
}

// Achievement is a common representation of an achievement
Expand Down
19 changes: 17 additions & 2 deletions models/game.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,28 @@ type ExtentedGameInfo struct {
IsFinal int `json:"IsFinal"`
RichPresencePatch string `json:"RichPresencePatch"`
GuideURL *string `json:"GuideURL"`
Updated time.Time `json:"Updated"`
Updated *time.Time `json:"Updated,omitempty"`
ConsoleName string `json:"ConsoleName"`
ParentGameID *int `json:"ParentGameID"`
NumDistinctPlayers int `json:"NumDistinctPlayers"`
NumAchievements int `json:"NumAchievements"`
Achievements map[int]GameAchievement `json:"Achievements"`
Claims []Claim `json:"Claims"`
Claims []Claim `json:"Claims,omitempty"`
NumDistinctPlayersCasual int `json:"NumDistinctPlayersCasual"`
NumDistinctPlayersHardcore int `json:"NumDistinctPlayersHardcore"`
}

type UserGameProgress struct {
ExtentedGameInfo
ReleasedAt *time.Time `json:"released_at"`
ReleasedAtGranularity *string `json:"released_at_granularity"`
PlayersTotal int `json:"players_total"`
AchievementsPublished int `json:"achievements_published"`
PointsTotal int `json:"points_total"`
NumAwardedToUser int `json:"NumAwardedToUser"`
NumAwardedToUserHardcore int `json:"NumAwardedToUserHardcore"`
UserCompletion string `json:"UserCompletion"`
UserCompletionHardcore string `json:"UserCompletionHardcore"`
HighestAwardKind *string `json:"HighestAwardKind"`
HighestAwardDate *RFC3339NumColonTZ `json:"HighestAwardDate"`
}
31 changes: 31 additions & 0 deletions models/time.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,3 +63,34 @@ func (lmd LongMonthDate) MarshalJSON() ([]byte, error) {
func (lmd *LongMonthDate) String() string {
return fmt.Sprintf("%q", lmd.Format(LongMonthDateFormat))
}

// RFC3339NumColonTZ is a time data structure that can be used for string dates formatted as "2006-01-02T15:04:05-07:00"
type RFC3339NumColonTZ struct {
time.Time
}

const (
RFC3339NumColonTZFormat = "2006-01-02T15:04:05-07:00"
)

func (r *RFC3339NumColonTZ) UnmarshalJSON(b []byte) (err error) {
s := strings.Trim(string(b), `"`)
if s == "" {
*r = RFC3339NumColonTZ{time.Time{}}
return nil
}
nt, err := time.Parse(RFC3339NumColonTZFormat, s)
if err != nil {
return err
}
*r = RFC3339NumColonTZ{nt}
return nil
}

func (r RFC3339NumColonTZ) MarshalJSON() ([]byte, error) {
return []byte(r.String()), nil
}

func (r *RFC3339NumColonTZ) String() string {
return fmt.Sprintf("%q", r.Format(RFC3339NumColonTZFormat))
}
53 changes: 53 additions & 0 deletions models/time_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -113,3 +113,56 @@ func TestLongMonthDateString(tt *testing.T) {
d := &models.LongMonthDate{t}
require.Equal(tt, `"`+expectedString+`"`, d.String())
}

func TestRFC3339NumColonTZUnmarshalJSON(tt *testing.T) {
tests := []struct {
name string
input string
assert func(t *testing.T, date *models.RFC3339NumColonTZ, err error)
}{
{
name: "empty string default",
input: "\"\"",
assert: func(t *testing.T, date *models.RFC3339NumColonTZ, err error) {
require.NotNil(t, date)
require.True(t, date.Time.IsZero())
require.NoError(t, err)
},
},
{
name: "unknown bytes",
input: "\"?>?>>L:\"",
assert: func(t *testing.T, date *models.RFC3339NumColonTZ, err error) {
require.NotNil(t, date)
require.True(t, date.Time.IsZero())
require.EqualError(t, err, "parsing time \"?>?>>L:\" as \"2006-01-02T15:04:05-07:00\": cannot parse \"?>?>>L:\" as \"2006\"")
},
},
{
name: "successfully unmarshal",
input: "\"2024-05-07T08:48:54+00:00\"",
assert: func(t *testing.T, date *models.RFC3339NumColonTZ, err error) {
ts, tErr := time.Parse(models.RFC3339NumColonTZFormat, "2024-05-07T08:48:54+00:00")
require.NoError(t, tErr)
require.NotNil(t, date)
require.Equal(t, ts, date.Time)
require.NoError(t, err)
},
},
}
for _, test := range tests {
tt.Run(test.name, func(t *testing.T) {
d := &models.RFC3339NumColonTZ{}
err := d.UnmarshalJSON([]byte(test.input))
test.assert(t, d, err)
})
}
}

func TestRFC3339NumColonTZString(tt *testing.T) {
expectedString := "2024-05-07T08:48:54+00:00"
t, err := time.Parse(models.RFC3339NumColonTZFormat, expectedString)
require.NoError(tt, err)
d := &models.RFC3339NumColonTZ{t}
require.Equal(tt, `"`+expectedString+`"`, d.String())
}
20 changes: 20 additions & 0 deletions user.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,3 +84,23 @@ func (c *Client) GetAchievementsEarnedOnDay(username string, date time.Time) ([]
}
return achievements, nil
}

// GetGameInfoAndUserProgress get metadata about a game as well as a user's progress on that game.
func (c *Client) GetGameInfoAndUserProgress(username string, game int, includeAwardMetadata bool) (*models.UserGameProgress, error) {
resp, err := c.do(
raHttp.Method(http.MethodGet),
raHttp.Path("/API/API_GetGameInfoAndUserProgress.php"),
raHttp.APIToken(c.Secret),
raHttp.Username(username),
raHttp.GameID(game),
raHttp.AwardMetadata(includeAwardMetadata),
)
if err != nil {
return nil, fmt.Errorf("calling endpoint: %w", err)
}
gameProgress, err := raHttp.ResponseObject[models.UserGameProgress](resp)
if err != nil {
return nil, fmt.Errorf("parsing response object: %w", err)
}
return gameProgress, nil
}
Loading

0 comments on commit f5f011f

Please sign in to comment.