Skip to content

Commit

Permalink
add GetGameTicketStats endpoint
Browse files Browse the repository at this point in the history
  • Loading branch information
joshraphael committed Nov 24, 2024
1 parent be6b0df commit 560f3cc
Show file tree
Hide file tree
Showing 9 changed files with 295 additions and 8 deletions.
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -123,4 +123,5 @@ For convenience, the API docs and examples can be found in the tables below
|-|-|-|
|`GetTicketByID()`|Gets ticket metadata information about a single achievement ticket, targeted by its ticket ID.|[docs](https://api-docs.retroachievements.org/v1/get-ticket-data/get-ticket-by-id.html) \| [example](examples/ticket/getticketbyid/getticketbyid.go)|
|`GetMostTicketedGames()`|Gets the games on the site with the highest count of opened achievement tickets.|[docs](https://api-docs.retroachievements.org/v1/get-ticket-data/get-most-ticketed-games.html) \| [example](examples/ticket/getmostticketedgames/getmostticketedgames.go)|
|`GetMostRecentTickets()`|Gets ticket metadata information about the latest opened achievement tickets on RetroAchievements.|[docs](https://api-docs.retroachievements.org/v1/get-ticket-data/get-most-recent-tickets.html) \| [example](examples/ticket/getmostrecenttickets/getmostrecenttickets.go)|
|`GetMostRecentTickets()`|Gets ticket metadata information about the latest opened achievement tickets on RetroAchievements.|[docs](https://api-docs.retroachievements.org/v1/get-ticket-data/get-most-recent-tickets.html) \| [example](examples/ticket/getmostrecenttickets/getmostrecenttickets.go)|
|`GetGameTicketStats()`|Gets ticket stats for a game, targeted by that game's unique ID.|[docs](https://api-docs.retroachievements.org/v1/get-ticket-data/get-game-ticket-stats.html) \| [example](examples/ticket/getgameticketstats/getgameticketstats.go)|
30 changes: 30 additions & 0 deletions examples/ticket/getgameticketstats/getgameticketstats.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// Package getgameticketstats provides an example for getting ticket stats for a game, targeted by that game's unique ID.
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 getgameticketstats.go`
*/
func main() {
secret := os.Getenv("RA_API_KEY")

client := retroachievements.NewClient(secret)

metadata := true
resp, err := client.GetGameTicketStats(models.GetGameTicketStatsParameters{
GameID: 1,
IncludeTicketMetadata: &metadata,
})
if err != nil {
panic(err)
}

fmt.Printf("%+v\n", resp)
}
4 changes: 3 additions & 1 deletion feed.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"fmt"
"net/http"
"strconv"
"time"

raHttp "github.com/joshraphael/go-retroachievements/http"
"github.com/joshraphael/go-retroachievements/models"
Expand All @@ -17,7 +18,8 @@ func (c *Client) GetRecentGameAwards(params models.GetRecentGameAwardsParameters
raHttp.APIToken(c.Secret),
}
if params.StartingDate != nil {
details = append(details, raHttp.D(*params.StartingDate))
d := *params.StartingDate
details = append(details, raHttp.D(d.UTC().Format(time.DateOnly)))
}
if params.Count != nil {
details = append(details, raHttp.C(*params.Count))
Expand Down
7 changes: 3 additions & 4 deletions http/request.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import (
"fmt"
"strconv"
"strings"
"time"
)

// Request holds values for an http call
Expand Down Expand Up @@ -88,10 +87,10 @@ func T(t int) RequestDetail {
})
}

// D adds a 'd' date to the query parameters
func D(d time.Time) RequestDetail {
// D adds a 'd' string to the query parameters
func D(d string) RequestDetail {
return requestDetailFn(func(r *Request) {
r.Params["d"] = d.UTC().Format(time.DateOnly)
r.Params["d"] = d
})
}

Expand Down
2 changes: 1 addition & 1 deletion http/request_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ func TestNewRequest(t *testing.T) {
raHttp.M(10),
raHttp.F(int(now.Unix())),
raHttp.T(int(later.Unix())),
raHttp.D(now),
raHttp.D(now.UTC().Format(time.DateOnly)),
raHttp.I([]string{strconv.Itoa(2837), strconv.Itoa(4535)}),
raHttp.K([]string{"test1", "test2"}),
raHttp.G(345),
Expand Down
45 changes: 45 additions & 0 deletions models/ticket.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,3 +90,48 @@ type GetMostRecentTicketsRecentTicket struct {
ReportStateDescription string `json:"ReportStateDescription"`
ReportTypeDescription string `json:"ReportTypeDescription"`
}

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

// [Optional] Get unofficial achievements (default: false)
Unofficial *bool

// [Optional] Return deep ticket metadata in the response's `Tickets` array (default: false)
IncludeTicketMetadata *bool
}

type GetGameTicketStats struct {
GameID int `json:"GameID"`
GameTitle string `json:"GameTitle"`
ConsoleName string `json:"ConsoleName"`
OpenTickets int `json:"OpenTickets"`
URL string `json:"URL"`
Tickets []GetGameTicketStatsTicket `json:"Tickets"`
}

type GetGameTicketStatsTicket struct {
ID int `json:"ID"`
AchievementID int `json:"AchievementID"`
AchievementTitle string `json:"AchievementTitle"`
AchievementDesc string `json:"AchievementDesc"`
AchievementType *string `json:"AchievementType"`
Points int `json:"Points"`
BadgeName string `json:"BadgeName"`
AchievementAuthor string `json:"AchievementAuthor"`
GameID int `json:"GameID"`
ConsoleName string `json:"ConsoleName"`
GameTitle string `json:"GameTitle"`
GameIcon string `json:"GameIcon"`
ReportedAt DateTime `json:"ReportedAt"`
ReportType int `json:"ReportType"`
ReportState int `json:"ReportState"`
Hardcore *int `json:"Hardcore"`
ReportNotes string `json:"ReportNotes"`
ReportedBy string `json:"ReportedBy"`
ResolvedAt *DateTime `json:"ResolvedAt"`
ResolvedBy *string `json:"ResolvedBy"`
ReportStateDescription string `json:"ReportStateDescription"`
ReportTypeDescription string `json:"ReportTypeDescription"`
}
25 changes: 25 additions & 0 deletions ticket.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,3 +77,28 @@ func (c *Client) GetMostRecentTickets(params models.GetMostRecentTicketsParamete
}
return resp, nil
}

// GetGameTicketStats gets ticket stats for a game, targeted by that game's unique ID.
func (c *Client) GetGameTicketStats(params models.GetGameTicketStatsParameters) (*models.GetGameTicketStats, error) {
details := []raHttp.RequestDetail{
raHttp.Method(http.MethodGet),
raHttp.Path("/API/API_GetTicketData.php"),
raHttp.APIToken(c.Secret),
raHttp.G(params.GameID),
}
if params.Unofficial != nil && *params.Unofficial {
details = append(details, raHttp.F(5))
}
if params.IncludeTicketMetadata != nil && *params.IncludeTicketMetadata {
details = append(details, raHttp.D(strconv.Itoa(1)))
}
r, err := c.do(details...)
if err != nil {
return nil, fmt.Errorf("calling endpoint: %w", err)
}
resp, err := raHttp.ResponseObject[models.GetGameTicketStats](r)
if err != nil {
return nil, fmt.Errorf("parsing response object: %w", err)
}
return resp, nil
}
184 changes: 184 additions & 0 deletions ticket_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -423,6 +423,10 @@ func TestGetMostRecentTickets(tt *testing.T) {
require.Equal(t, reportedAt, resp.RecentTickets[0].ReportedAt.Time)
require.Equal(t, 0, resp.RecentTickets[0].ReportType)
require.Equal(t, 2, resp.RecentTickets[0].ReportState)
require.NotNil(t, resp.RecentTickets[0].Hardcore)
require.Equal(t, hardcore, *resp.RecentTickets[0].Hardcore)
require.NotNil(t, resp.RecentTickets[0].ResolvedAt)
require.Equal(t, resolvedAt, resp.RecentTickets[0].ResolvedAt.Time)
require.NotNil(t, resp.RecentTickets[0].ResolvedBy)
require.Equal(t, resolvedBy, *resp.RecentTickets[0].ResolvedBy)
require.Equal(t, "Resolved", resp.RecentTickets[0].ReportStateDescription)
Expand Down Expand Up @@ -455,3 +459,183 @@ func TestGetMostRecentTickets(tt *testing.T) {
})
}
}

func TestGetGameTicketStats(tt *testing.T) {
unofficial := true
metadata := true
achievementType := "progression"
reportedAt, err := time.Parse(time.DateTime, "2014-02-22 23:23:53")
require.NoError(tt, err)
hardcore := 1
resolvedAt, err := time.Parse(time.DateTime, "2014-02-24 22:51:10")
require.NoError(tt, err)
resolvedBy := "Scott"
tests := []struct {
name string
params models.GetGameTicketStatsParameters
modifyURL func(url string) string
responseCode int
responseMessage models.GetGameTicketStats
responseError models.ErrorResponse
response func(messageBytes []byte, errorBytes []byte) []byte
assert func(t *testing.T, resp *models.GetGameTicketStats, err error)
}{
{
name: "fail to call endpoint",
params: models.GetGameTicketStatsParameters{
GameID: 1,
Unofficial: &unofficial,
IncludeTicketMetadata: &metadata,
},
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.GetGameTicketStats, err error) {
require.Nil(t, resp)
require.EqualError(t, err, "calling endpoint: Get \"/API/API_GetTicketData.php?d=1&f=5&g=1&y=some_secret\": unsupported protocol scheme \"\"")
},
},
{
name: "error response",
params: models.GetGameTicketStatsParameters{
GameID: 1,
Unofficial: &unofficial,
IncludeTicketMetadata: &metadata,
},
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.GetGameTicketStats, 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.GetGameTicketStatsParameters{
GameID: 1,
Unofficial: &unofficial,
IncludeTicketMetadata: &metadata,
},
modifyURL: func(url string) string {
return url
},
responseCode: http.StatusOK,
responseMessage: models.GetGameTicketStats{
GameID: 20,
GameTitle: "Alex Kidd in the Enchanted Castle",
ConsoleName: "Genesis/Mega Drive",
OpenTickets: 4,
URL: "https://retroachievements.org/game/20/tickets",
Tickets: []models.GetGameTicketStatsTicket{
{
ID: 1,
AchievementID: 3778,
AchievementTitle: "Exhibition Match",
AchievementDesc: "Complete Stage 3: Flugelheim Museum",
AchievementType: &achievementType,
Points: 10,
BadgeName: "04357",
AchievementAuthor: "Batman",
GameID: 45,
ConsoleName: "Genesis/Mega Drive",
GameTitle: "Batman: The Video Game",
GameIcon: "/Images/053393.png",
ReportedAt: models.DateTime{
Time: reportedAt,
},
ReportType: 0,
ReportState: 2,
Hardcore: &hardcore,
ReportNotes: "This achievement didn't trigger for some reason?",
ReportedBy: "qwe",
ResolvedAt: &models.DateTime{
Time: resolvedAt,
},
ResolvedBy: &resolvedBy,
ReportStateDescription: "Resolved",
ReportTypeDescription: "Invalid ticket type",
},
},
},
response: func(messageBytes []byte, errorBytes []byte) []byte {
return messageBytes
},
assert: func(t *testing.T, resp *models.GetGameTicketStats, err error) {
require.NotNil(t, resp)
require.Equal(t, 20, resp.GameID)
require.Equal(t, "Alex Kidd in the Enchanted Castle", resp.GameTitle)
require.Equal(t, "Genesis/Mega Drive", resp.ConsoleName)
require.Equal(t, 4, resp.OpenTickets)
require.Equal(t, "https://retroachievements.org/game/20/tickets", resp.URL)
require.Len(t, resp.Tickets, 1)
require.Equal(t, 1, resp.Tickets[0].ID)
require.Equal(t, 3778, resp.Tickets[0].AchievementID)
require.Equal(t, "Exhibition Match", resp.Tickets[0].AchievementTitle)
require.Equal(t, "Complete Stage 3: Flugelheim Museum", resp.Tickets[0].AchievementDesc)
require.NotNil(t, resp.Tickets[0].AchievementType)
require.Equal(t, achievementType, *resp.Tickets[0].AchievementType)
require.Equal(t, 10, resp.Tickets[0].Points)
require.Equal(t, "04357", resp.Tickets[0].BadgeName)
require.Equal(t, "Batman", resp.Tickets[0].AchievementAuthor)
require.Equal(t, 45, resp.Tickets[0].GameID)
require.Equal(t, "Genesis/Mega Drive", resp.Tickets[0].ConsoleName)
require.Equal(t, "Batman: The Video Game", resp.Tickets[0].GameTitle)
require.Equal(t, "/Images/053393.png", resp.Tickets[0].GameIcon)
require.Equal(t, reportedAt, resp.Tickets[0].ReportedAt.Time)
require.Equal(t, 0, resp.Tickets[0].ReportType)
require.Equal(t, 2, resp.Tickets[0].ReportState)
require.NotNil(t, resp.Tickets[0].Hardcore)
require.Equal(t, hardcore, *resp.Tickets[0].Hardcore)
require.NotNil(t, resp.Tickets[0].ResolvedAt)
require.Equal(t, resolvedAt, resp.Tickets[0].ResolvedAt.Time)
require.NotNil(t, resp.Tickets[0].ResolvedBy)
require.Equal(t, resolvedBy, *resp.Tickets[0].ResolvedBy)
require.Equal(t, "Resolved", resp.Tickets[0].ReportStateDescription)
require.Equal(t, "Invalid ticket type", resp.Tickets[0].ReportTypeDescription)
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_GetTicketData.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.GetGameTicketStats(test.params)
test.assert(t, resp, err)
})
}
}
3 changes: 2 additions & 1 deletion user.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"fmt"
"net/http"
"strconv"
"time"

raHttp "github.com/joshraphael/go-retroachievements/http"
"github.com/joshraphael/go-retroachievements/models"
Expand Down Expand Up @@ -76,7 +77,7 @@ func (c *Client) GetAchievementsEarnedOnDay(params models.GetAchievementsEarnedO
raHttp.Path("/API/API_GetAchievementsEarnedOnDay.php"),
raHttp.APIToken(c.Secret),
raHttp.U(params.Username),
raHttp.D(params.Date),
raHttp.D(params.Date.UTC().Format(time.DateOnly)),
)
if err != nil {
return nil, fmt.Errorf("calling endpoint: %w", err)
Expand Down

0 comments on commit 560f3cc

Please sign in to comment.