Skip to content

Commit

Permalink
feat: add subcommands for awards and current game status
Browse files Browse the repository at this point in the history
  • Loading branch information
surskitt committed Aug 31, 2024
1 parent afc7077 commit 456f439
Show file tree
Hide file tree
Showing 5 changed files with 2,996 additions and 14 deletions.
6 changes: 5 additions & 1 deletion main.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,9 +103,13 @@ func raHandler(client *req.Client, kv *bolt.DB, m *gowon.Message) (string, error
return CommandHandler(client, kv, m.Nick, user, raCurrentStatus)
case "p", "points":
return CommandHandler(client, kv, m.Nick, user, raPoints)
case "w", "awards":
return CommandHandler(client, kv, m.Nick, user, raAwards)
case "g", "game":
return CommandHandler(client, kv, m.Nick, user, raGameProgress)
}

return "one of [s]et, [a]chievement, [l]ast, [c]urrent or [p]oints must be passed as a command", nil
return "one of [s]et, [a]chievement, [l]ast, [c]urrent, [p]oints, a[w]ards or [g]ame must be passed as a command", nil
}

func main() {
Expand Down
188 changes: 177 additions & 11 deletions retroachivements.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package main

import (
"fmt"
"strconv"
"strings"
"time"

Expand All @@ -11,17 +12,25 @@ import (
const (
timeDateFormat = "2006-01-02 15:04:05"

raAchievementsURL = "https://retroachievements.org/API/API_GetUserRecentAchievements.php"
raRecentGamesURL = "https://retroachievements.org/API/API_GetUserRecentlyPlayedGames.php"
raUserSummaryURL = "https://retroachievements.org/API/API_GetUserSummary.php"

achievementColour = "cyan"
gameColour = "magenta"
pointsColour = "green"
relaxedPointsColour = "magenta"
hardcoreColour = "yellow"
richPresenceColour = "yellow"
rankColour = "yellow"
raRootURL = "https://retroachievements.org/API/"
raAchievementsURL = raRootURL + "API_GetUserRecentAchievements.php"
raRecentGamesURL = raRootURL + "API_GetUserRecentlyPlayedGames.php"
raUserSummaryURL = raRootURL + "API_GetUserSummary.php"
raAwardsURL = raRootURL + "API_GetUserAwards.php"
raGameProgressURL = raRootURL + "API_GetGameInfoAndUserProgress.php"

achievementColour = "cyan"
gameColour = "magenta"
pointsColour = "green"
relaxedPointsColour = "magenta"
hardcoreColour = "yellow"
richPresenceColour = "yellow"
rankColour = "yellow"
awardColour = "yellow"
beatenColour = "red"
completedColour = "cyan"
masteredColour = "yellow"
completionPercentColour = "blue"
)

var (
Expand Down Expand Up @@ -54,6 +63,7 @@ type Achievement struct {
Points int `json:"Points"`
GameTitle string `json:"GameTitle"`
ConsoleName string `json:"ConsoleName"`
GameID int `json:"GameID"`
}

func formatAchievement(a Achievement) string {
Expand Down Expand Up @@ -242,3 +252,159 @@ func raPoints(client *req.Client, user string) (string, error) {

return sb.String(), nil
}

type Awards struct {
BeatenHardcore int `json:"BeatenHardcoreAwardsCount"`
BeatenSoftcore int `json:"BeatenSoftcoreAwardsCount"`
Completed int `json:"CompletionAwardsCount"`
Mastered int `json:"MasteryAwardsCount"`
}

func raAwards(client *req.Client, user string) (string, error) {
var j Awards

_, err := client.R().
SetQueryParam("u", user).
SetSuccessResult(&j).
Get(raAwardsURL)

if err != nil {
return "", err
}

var sb strings.Builder

w := func(in, colour string) {
s := colourString(in, colour)
sb.WriteString(s)
}

sb.WriteString(fmt.Sprintf("%s | ", user))

w(fmt.Sprintf("Beaten: %d (Relaxed: %d)", j.BeatenHardcore, j.BeatenSoftcore), beatenColour)

sb.WriteString(" | ")

w(fmt.Sprintf("Completed: %d", j.Completed), completedColour)

sb.WriteString(" | ")

w(fmt.Sprintf("Mastered: %d", j.Mastered), masteredColour)

return sb.String(), nil
}

type GameProgress struct {
Title string `json:"Title"`
Console string `json:"ConsoleName"`
Completion string `json:"UserCompletion"`
CompletionHardcore string `json:"UserCompletionHardcore"`
NumAchievements int `json:"NumAchievements"`
AchievementsRelaxed int `json:"NumAwardedToUser"`
AchievementsHardcore int `json:"NumAwardedToUserHardcore"`
Achievements map[string]struct {
Points int `json:"Points"`
DateEarned string `json:"DateEarned"`
} `json:"Achievements"`
PointsTotal int `json:"points_total"`
HighestAward string `json:"HighestAwardKind"`
}

func (gp *GameProgress) PointsAwarded() string {
points := 0
pointsAwarded := 0

for _, a := range gp.Achievements {
points += a.Points

if a.DateEarned != "" {
pointsAwarded += a.Points
}
}

return fmt.Sprintf("%d/%d", pointsAwarded, points)
}

func raGameProgress(client *req.Client, user string) (string, error) {
var aj []Achievement

_, err := client.R().
SetQueryParam("u", user).
SetQueryParam("m", "131400").
SetSuccessResult(&aj).
Get(raAchievementsURL)

if err != nil {
return "", err
}

if len(aj) == 0 {
return fmt.Sprintf("No played games found for user %s", user), nil
}

gameID := strconv.Itoa(aj[0].GameID)

var gj GameProgress

_, err = client.R().
SetQueryParam("u", user).
SetQueryParam("g", string(gameID)).
SetQueryParam("a", "1").
SetSuccessResult(&gj).
Get(raGameProgressURL)

if err != nil {
return "", err
}

var sb strings.Builder

w := func(in, colour string) {
s := colourString(in, colour)
sb.WriteString(s)
}

sb.WriteString(fmt.Sprintf("%s | ", user))

w(fmt.Sprintf("%s (%s)", gj.Title, gj.Console), gameColour)

sb.WriteString(" | ")

var cb strings.Builder
cb.WriteString(fmt.Sprintf("Completion: %s", gj.CompletionHardcore))

if gj.CompletionHardcore != gj.Completion {
cb.WriteString(fmt.Sprintf(" (Relaxed: %s)", gj.Completion))
}

w(cb.String(), completionPercentColour)

sb.WriteString(" | ")

var ab strings.Builder
ab.WriteString(fmt.Sprintf("Achievements: %d/%d", gj.AchievementsHardcore, gj.NumAchievements))

if gj.AchievementsHardcore != gj.AchievementsRelaxed {
ab.WriteString(fmt.Sprintf(" (Relaxed: %d)", gj.AchievementsRelaxed))
}

w(ab.String(), achievementColour)

sb.WriteString(" | ")

w(fmt.Sprintf("Points: %s", gj.PointsAwarded()), pointsColour)

if gj.HighestAward != "" {
awards := map[string]string{
"beaten-softcore": "Beaten",
"beaten-hardcore": "Beaten [Hardocre]",
"completed": "Completed",
"mastered": "Mastered",
}

sb.WriteString(" | ")
w(awards[gj.HighestAward], awardColour)
}

return sb.String(), nil
}
98 changes: 96 additions & 2 deletions retroachivements_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -230,7 +230,7 @@ func TestRaCurrentStatus(t *testing.T) {
for name, tc := range cases {
t.Run(name, func(t *testing.T) {
now = func() time.Time { n, _ := time.Parse(timeDateFormat, tc.now); return n }
json := openTestFile(t, "API_GetUserSummary", "summary.json")
json := openTestFile(t, "API_GetUserSummary", tc.jsonfn)

client := req.C()
httpmock.ActivateNonDefault(client.GetClient())
Expand Down Expand Up @@ -262,7 +262,7 @@ func TestRaPoints(t *testing.T) {

for name, tc := range cases {
t.Run(name, func(t *testing.T) {
json := openTestFile(t, "API_GetUserSummary", "summary.json")
json := openTestFile(t, "API_GetUserSummary", tc.jsonfn)

client := req.C()
httpmock.ActivateNonDefault(client.GetClient())
Expand All @@ -278,3 +278,97 @@ func TestRaPoints(t *testing.T) {
})
}
}

func TestRaAwards(t *testing.T) {
cases := map[string]struct {
jsonfn string
expected string
err error
}{
"awards": {
jsonfn: "awards.json",
expected: "user | {red}Beaten: 1 (Relaxed: 5){clear} | {cyan}Completed: 3{clear} | {yellow}Mastered: 0{clear}",
err: nil,
},
}

for name, tc := range cases {
t.Run(name, func(t *testing.T) {
json := openTestFile(t, "API_GetUserAwards", tc.jsonfn)

client := req.C()
httpmock.ActivateNonDefault(client.GetClient())
httpmock.RegisterResponder("GET", raAwardsURL, func(request *http.Request) (*http.Response, error) {
resp := httpmock.NewBytesResponse(http.StatusOK, json)
return resp, nil
})

out, err := raAwards(client, "user")

assert.Equal(t, tc.expected, out)
assert.ErrorIs(t, tc.err, err)
})
}
}

func TestGameProgressPointsAwarded(t *testing.T) {
cases := map[string]struct {
jsonfn string
expected string
}{
"points": {
jsonfn: "progress.json",
expected: "419/1369",
},
}

for name, tc := range cases {
t.Run(name, func(t *testing.T) {
j := openTestFile(t, "API_GetGameInfoAndUserProgress", tc.jsonfn)
gp := GameProgress{}
err := json.Unmarshal(j, &gp)
assert.Nil(t, err)

assert.Equal(t, tc.expected, gp.PointsAwarded())
})
}
}

func TestRaGameProgress(t *testing.T) {
cases := map[string]struct {
jsonfn string
expected string
err error
}{
"progress": {
jsonfn: "progress.json",
expected: "user | {magenta}~Hack~ Pokemon Radical Red (Game Boy Advance){clear} | {blue}Completion: 0.64% (Relaxed: 33.12%){clear} | {cyan}Achievements: 1/157 (Relaxed: 52){clear} | {green}Points: 419/1369{clear} | {yellow}Completed{clear}",
err: nil,
},
}

for name, tc := range cases {
t.Run(name, func(t *testing.T) {
aJson := openTestFile(t, "API_GetUserRecentAchievements", "many_achievements.json")
gpJson := openTestFile(t, "API_GetGameInfoAndUserProgress", tc.jsonfn)

client := req.C()
httpmock.ActivateNonDefault(client.GetClient())

httpmock.RegisterResponder("GET", raAchievementsURL, func(request *http.Request) (*http.Response, error) {
resp := httpmock.NewBytesResponse(http.StatusOK, aJson)
return resp, nil
})

httpmock.RegisterResponder("GET", raGameProgressURL, func(request *http.Request) (*http.Response, error) {
resp := httpmock.NewBytesResponse(http.StatusOK, gpJson)
return resp, nil
})

out, err := raGameProgress(client, "user")

assert.Equal(t, tc.expected, out)
assert.ErrorIs(t, tc.err, err)
})
}
}
Loading

0 comments on commit 456f439

Please sign in to comment.