From a3d9eb97a910f6e5d2d3fa8f06a93f9e305b9a7e Mon Sep 17 00:00:00 2001 From: Brian Buller Date: Thu, 21 Nov 2019 10:28:14 -0600 Subject: [PATCH] Initial Commit --- aoc.go | 144 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 144 insertions(+) create mode 100644 aoc.go diff --git a/aoc.go b/aoc.go new file mode 100644 index 0000000..05afa1f --- /dev/null +++ b/aoc.go @@ -0,0 +1,144 @@ +package aoc + +import ( + "encoding/json" + "errors" + "fmt" + "io/ioutil" + "net/http" + "strconv" + "strings" + "time" +) + +type AoC struct { + session string + boardId string + boards map[int]*Leaderboard +} + +func NewAoC(boardId, session string) (*AoC, error) { + if boardId == "" { + return nil, errors.New("Board ID is required") + } + if session == "" { + return nil, errors.New("Session key is required") + } + return &AoC{ + session: session, + boardId: boardId, + boards: make(map[int]*Leaderboard), + }, nil +} + +func (a *AoC) GetCachedLeaderboard(year int) (*Leaderboard, error) { + if year < 2015 || year > time.Now().Year() { + return nil, errors.New("Invalid Year") + } + if board, ok := a.boards[year]; ok { + return board, nil + } + return nil, errors.New(fmt.Sprintf("Leaderboard (%d) is not cached", year)) +} + +func (a *AoC) GetLeaderboard(year int) (*Leaderboard, error) { + if year < 2015 || year > time.Now().Year() { + return nil, errors.New("Invalid Year") + } + if board, ok := a.boards[year]; ok { + if time.Since(board.LastFetch) < (time.Minute * 10) { + return board, nil + } + } + var err error + a.boards[year], err = a.fetchLeaderboard(year) + if err != nil { + if err.Error() == "invalid character '<' looking for beginning of value" { + return nil, errors.New("Invalid Session Cookie") + } + return nil, err + } + return a.boards[year], nil +} + +func (a *AoC) fetchLeaderboard(year int) (*Leaderboard, error) { + var err error + var req *http.Request + var resp *http.Response + var body []byte + leaderboard := new(Leaderboard) + leaderboard.Members = make(map[string]Member) + + client := &http.Client{} + boardString := fmt.Sprintf("https://adventofcode.com/%d/leaderboard/private/view/%s.json", year, a.boardId) + + req, err = http.NewRequest("GET", boardString, nil) + req.Header.Add("Cookie", "session="+a.session) + resp, err = client.Do(req) + if err != nil { + return nil, err + } + defer resp.Body.Close() + body, err = ioutil.ReadAll(resp.Body) + if err != nil { + return nil, err + } + strBody := string(body) + strBody = strings.ReplaceAll(strBody, "\"last_star_ts\":0", "\"last_star_ts\":\"0\"") + err = json.Unmarshal([]byte(strBody), &leaderboard) + if err != nil { + return nil, err + } + for k, mbr := range leaderboard.Members { + starTs, err := strconv.ParseInt(mbr.RawStarTs, 10, 64) + if err != nil { + continue + } + mbr.LastStarTs = time.Unix(starTs, 0) + leaderboard.Members[k] = mbr + } + leaderboard.LastFetch = time.Now() + a.boards[year] = leaderboard + return leaderboard, err +} + +type Leaderboard struct { + OwnerID string `json:"owner_id"` + Event string `json:"event"` + Members map[string]Member `json:"members"` + LastFetch time.Time +} + +type Member struct { + ID string `json:"id"` + Name string `json:"name"` + Stars int `json:"stars"` + RawStarTs string `json:"last_star_ts"` + LocalScore int `json:"local_score"` + GlobalScore int `json:"global_score"` + LastStarTs time.Time + SlackID string +} + +type ByLocalScore []Member + +func (a ByLocalScore) Len() int { return len(a) } +func (a ByLocalScore) Swap(i, j int) { a[i], a[j] = a[j], a[i] } +func (a ByLocalScore) Less(i, j int) bool { return a[i].LocalScore < a[j].LocalScore } + +type ByStars []Member + +func (a ByStars) Len() int { return len(a) } +func (a ByStars) Swap(i, j int) { a[i], a[j] = a[j], a[i] } +func (a ByStars) Less(i, j int) bool { return a[i].Stars < a[j].Stars } + +type ByStarsThenScore []Member + +func (a ByStarsThenScore) Len() int { return len(a) } +func (a ByStarsThenScore) Swap(i, j int) { a[i], a[j] = a[j], a[i] } +func (a ByStarsThenScore) Less(i, j int) bool { + if a[i].Stars == a[j].Stars { + return a[i].LocalScore < a[j].LocalScore + } + return a[i].Stars < a[j].Stars +}