initial commit
This commit is contained in:
90
internal/statsapi/client.go
Normal file
90
internal/statsapi/client.go
Normal file
@@ -0,0 +1,90 @@
|
||||
package statsapi
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
var DefaultClient = NewClient(http.DefaultClient)
|
||||
|
||||
func Schedule(sportId, teamId string) (ScheduleResponse, error) {
|
||||
return DefaultClient.Schedule(sportId, teamId)
|
||||
}
|
||||
|
||||
func Feed(gamePk string) (FeedResponse, error) {
|
||||
return DefaultClient.Feed(gamePk)
|
||||
}
|
||||
|
||||
func DiffPatch(gamePk, startTimecode, pushUpdateId string) (DiffPatchResponse, error) {
|
||||
return DefaultClient.DiffPatch(gamePk, startTimecode, pushUpdateId)
|
||||
}
|
||||
|
||||
type Client struct {
|
||||
baseURL url.URL
|
||||
httpClient *http.Client
|
||||
}
|
||||
|
||||
func NewClient(c *http.Client) *Client {
|
||||
return &Client{
|
||||
baseURL: url.URL{
|
||||
Scheme: "https",
|
||||
Host: "statsapi.mlb.com",
|
||||
},
|
||||
httpClient: c,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) Schedule(sportId, teamId string) (ScheduleResponse, error) {
|
||||
endpoint := url.URL{Path: "api/v1/schedule"}
|
||||
query := endpoint.Query()
|
||||
query.Add("sportId", sportId)
|
||||
query.Add("teamId", teamId)
|
||||
endpoint.RawQuery = query.Encode()
|
||||
|
||||
url := c.baseURL.ResolveReference(&endpoint)
|
||||
|
||||
var schedule ScheduleResponse
|
||||
err := c.get(url.String(), &schedule)
|
||||
return schedule, err
|
||||
}
|
||||
|
||||
func (c *Client) Feed(gamePk string) (FeedResponse, error) {
|
||||
endpoint := url.URL{Path: "api/v1.1/game/" + gamePk + "/feed/live"}
|
||||
url := c.baseURL.ResolveReference(&endpoint)
|
||||
|
||||
var feed FeedResponse
|
||||
err := c.get(url.String(), &feed)
|
||||
return feed, err
|
||||
}
|
||||
|
||||
func (c *Client) DiffPatch(gamePk, startTimecode, pushUpdateId string) (DiffPatchResponse, error) {
|
||||
endpoint := url.URL{Path: "api/v1.1/game/" + gamePk + "/feed/live/diffPatch"}
|
||||
query := endpoint.Query()
|
||||
query.Add("language", "en")
|
||||
query.Add("startTimecode", startTimecode)
|
||||
query.Add("pushUpdateId", pushUpdateId)
|
||||
endpoint.RawQuery = query.Encode()
|
||||
|
||||
url := c.baseURL.ResolveReference(&endpoint)
|
||||
|
||||
var diffPatch DiffPatchResponse
|
||||
err := c.get(url.String(), &diffPatch)
|
||||
return diffPatch, err
|
||||
}
|
||||
|
||||
func (c *Client) get(url string, v any) error {
|
||||
req, err := http.NewRequest("GET", url, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
resp, err := c.httpClient.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
err = json.NewDecoder(resp.Body).Decode(v)
|
||||
return err
|
||||
}
|
||||
34
internal/statsapi/common.go
Normal file
34
internal/statsapi/common.go
Normal file
@@ -0,0 +1,34 @@
|
||||
package statsapi
|
||||
|
||||
var TeamIds = map[string]int{
|
||||
"laa": 108,
|
||||
"az": 109,
|
||||
"bal": 110,
|
||||
"bos": 111,
|
||||
"chc": 112,
|
||||
"cin": 113,
|
||||
"cle": 114,
|
||||
"col": 115,
|
||||
"det": 116,
|
||||
"hou": 117,
|
||||
"kc": 118,
|
||||
"lad": 119,
|
||||
"wsh": 120,
|
||||
"nym": 121,
|
||||
"oak": 133,
|
||||
"pit": 134,
|
||||
"sd": 135,
|
||||
"sea": 136,
|
||||
"sf": 137,
|
||||
"stl": 138,
|
||||
"tb": 139,
|
||||
"tex": 140,
|
||||
"tor": 141,
|
||||
"min": 142,
|
||||
"phi": 143,
|
||||
"atl": 144,
|
||||
"cws": 145,
|
||||
"mia": 146,
|
||||
"nyy": 147,
|
||||
"mil": 158,
|
||||
}
|
||||
70
internal/statsapi/feed.go
Normal file
70
internal/statsapi/feed.go
Normal file
@@ -0,0 +1,70 @@
|
||||
package statsapi
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
)
|
||||
|
||||
type FeedParams struct {
|
||||
GamePk string
|
||||
}
|
||||
|
||||
type FeedResponse struct {
|
||||
MetaData metadata
|
||||
LiveData livedata
|
||||
}
|
||||
|
||||
type metadata struct {
|
||||
TimeStamp string
|
||||
}
|
||||
|
||||
type livedata struct {
|
||||
Plays plays
|
||||
}
|
||||
|
||||
type plays struct {
|
||||
AllPlays []Play
|
||||
CurrentPlay Play
|
||||
ScoringPlays []json.Number
|
||||
}
|
||||
|
||||
type Play struct {
|
||||
Result result
|
||||
About about
|
||||
AtBatIndex int
|
||||
}
|
||||
|
||||
type result struct {
|
||||
Event string
|
||||
Description string
|
||||
RBI int
|
||||
AwayScore int
|
||||
HomeScore int
|
||||
}
|
||||
|
||||
type about struct {
|
||||
AtBatIndex json.Number
|
||||
IsTopInning bool
|
||||
Inning json.Number
|
||||
IsScoringPlay bool
|
||||
CaptivatingIndex json.Number
|
||||
}
|
||||
|
||||
func (p *Play) Patch(patch Patch) {
|
||||
instructions := patch.Diff
|
||||
stem := "/liveData/plays/currentPlay"
|
||||
for _, i := range instructions {
|
||||
if i.Op == "add" && i.Path == stem+"/result/description" {
|
||||
p.Result.Description = i.Value.(string)
|
||||
} else if i.Op == "replace" && i.Path == stem+"/about/isScoringPlay" {
|
||||
p.About.IsScoringPlay = i.Value.(bool)
|
||||
} else if i.Op == "replace" && i.Path == stem+"/result/homeScore" {
|
||||
p.Result.HomeScore = int(i.Value.(float64))
|
||||
} else if i.Op == "replace" && i.Path == stem+"/result/awayScore" {
|
||||
p.Result.AwayScore = int(i.Value.(float64))
|
||||
} else if i.Op == "add" && i.Path == stem+"/result/event" {
|
||||
p.Result.Event = i.Value.(string)
|
||||
} else if i.Op == "replace" && i.Path == stem+"/atBatIndex" {
|
||||
p.AtBatIndex = int(i.Value.(float64))
|
||||
}
|
||||
}
|
||||
}
|
||||
34
internal/statsapi/patch.go
Normal file
34
internal/statsapi/patch.go
Normal file
@@ -0,0 +1,34 @@
|
||||
package statsapi
|
||||
|
||||
import (
|
||||
"errors"
|
||||
)
|
||||
|
||||
type DiffPatchParams struct {
|
||||
GamePk string
|
||||
StartTimecode string
|
||||
PushUpdateId string
|
||||
}
|
||||
|
||||
type DiffPatchResponse []Patch
|
||||
|
||||
type Patch struct {
|
||||
Diff []instruction
|
||||
}
|
||||
|
||||
type instruction struct {
|
||||
Op string
|
||||
Path string
|
||||
Value any
|
||||
From string
|
||||
}
|
||||
|
||||
func (p *Patch) Timestamp() (string, error) {
|
||||
for _, d := range p.Diff {
|
||||
if d.Op == "replace" && d.Path == "/metaData/timeStamp" {
|
||||
return d.Value.(string), nil
|
||||
}
|
||||
}
|
||||
|
||||
return "", errors.New("could not find replacement timestamp")
|
||||
}
|
||||
31
internal/statsapi/schedule.go
Normal file
31
internal/statsapi/schedule.go
Normal file
@@ -0,0 +1,31 @@
|
||||
package statsapi
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
)
|
||||
|
||||
type ScheduleParams struct {
|
||||
SportId string
|
||||
TeamId string
|
||||
}
|
||||
|
||||
type ScheduleResponse struct {
|
||||
TotalGames json.Number
|
||||
TotalGamesInProgress json.Number
|
||||
Dates []date
|
||||
}
|
||||
|
||||
type date struct {
|
||||
TotalGames json.Number
|
||||
TotalGamesInProgress json.Number
|
||||
Games []game
|
||||
}
|
||||
|
||||
type game struct {
|
||||
GamePk json.Number
|
||||
Content content
|
||||
}
|
||||
|
||||
type content struct {
|
||||
Link string
|
||||
}
|
||||
59
internal/statsapi/websocket.go
Normal file
59
internal/statsapi/websocket.go
Normal file
@@ -0,0 +1,59 @@
|
||||
package statsapi
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
)
|
||||
|
||||
type Push struct {
|
||||
UpdateId string
|
||||
}
|
||||
|
||||
type GamedayWebsocket struct {
|
||||
baseURL url.URL
|
||||
*websocket.Conn
|
||||
}
|
||||
|
||||
func NewGamedayWebsocket(gamePk string) (GamedayWebsocket, error) {
|
||||
ws := GamedayWebsocket{
|
||||
baseURL: url.URL{
|
||||
Scheme: "wss",
|
||||
Host: "ws.statsapi.mlb.com",
|
||||
},
|
||||
}
|
||||
|
||||
err := ws.init(gamePk)
|
||||
return ws, err
|
||||
}
|
||||
|
||||
func (g *GamedayWebsocket) init(gamePk string) error {
|
||||
endpoint := url.URL{
|
||||
Path: "api/v1/game/push/subscribe/gameday/" + gamePk,
|
||||
}
|
||||
|
||||
url := g.baseURL.ResolveReference(&endpoint)
|
||||
conn, _, err := websocket.DefaultDialer.Dial(url.String(), nil)
|
||||
|
||||
g.Conn = conn
|
||||
return err
|
||||
}
|
||||
|
||||
func (g *GamedayWebsocket) SendKeepAlive() error {
|
||||
msg := []byte("Gameday5")
|
||||
err := g.Conn.WriteMessage(websocket.TextMessage, msg)
|
||||
return err
|
||||
}
|
||||
|
||||
func (g *GamedayWebsocket) KeepAlive(interval time.Duration, ch chan<- error) {
|
||||
ticker := time.NewTicker(interval)
|
||||
defer ticker.Stop()
|
||||
|
||||
for range ticker.C {
|
||||
err := g.SendKeepAlive()
|
||||
if err != nil {
|
||||
ch <- err
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user