diff --git a/src/engine/segment.go b/src/engine/segment.go index e4403ea8..fe99ebda 100644 --- a/src/engine/segment.go +++ b/src/engine/segment.go @@ -170,6 +170,8 @@ const ( LUA SegmentType = "lua" // MERCURIAL writes the Mercurial source control information MERCURIAL SegmentType = "mercurial" + // NBA writes NBA game data + NBA SegmentType = "nba" // NBGV writes the nbgv version information NBGV SegmentType = "nbgv" // NIGHTSCOUT is an open source diabetes system @@ -296,6 +298,7 @@ var Segments = map[SegmentType]func() SegmentWriter{ KUBECTL: func() SegmentWriter { return &segments.Kubectl{} }, LUA: func() SegmentWriter { return &segments.Lua{} }, MERCURIAL: func() SegmentWriter { return &segments.Mercurial{} }, + NBA: func() SegmentWriter { return &segments.Nba{} }, NBGV: func() SegmentWriter { return &segments.Nbgv{} }, NIGHTSCOUT: func() SegmentWriter { return &segments.Nightscout{} }, NODE: func() SegmentWriter { return &segments.Node{} }, diff --git a/src/segments/nba.go b/src/segments/nba.go new file mode 100644 index 00000000..7ba753b2 --- /dev/null +++ b/src/segments/nba.go @@ -0,0 +1,418 @@ +package segments + +import ( + "encoding/json" + "errors" + "fmt" + "time" + + "github.com/jandedobbeleer/oh-my-posh/src/platform" + "github.com/jandedobbeleer/oh-my-posh/src/properties" +) + +// segment struct, makes templating easier +type Nba struct { + props properties.Properties + env platform.Environment + + NBAData +} + +// NBA struct contains parsed API data that care about for the segment +type NBAData struct { + HomeTeam string + AwayTeam string + Time string + GameDate string + StartTimeUTC string + GameStatus GameStatus // 1 = scheduled, 2 = in progress, 3 = finished + HomeScore int + AwayScore int + HomeTeamWins int + HomeTeamLosses int + AwayTeamWins int + AwayTeamLosses int +} + +func (nba *NBAData) HasStats() bool { + return nba.HomeTeamWins != 0 || nba.HomeTeamLosses != 0 || nba.AwayTeamWins != 0 || nba.AwayTeamLosses != 0 +} + +func (nba *NBAData) Started() bool { + return nba.GameStatus == InProgress || nba.GameStatus == Finished +} + +const ( + NBASeason properties.Property = "season" + TeamName properties.Property = "team" + DaysOffset properties.Property = "days_offset" + + ScheduledTemplate properties.Property = "scheduled_template" + InProgressTemplate properties.Property = "in_progress_template" + FinishedTemplate properties.Property = "finished_template" + + NBAScoreURL string = "https://cdn.nba.com/static/json/liveData/scoreboard/todaysScoreboard_00.json" + NBASchedURLPart1 string = "https://stats.nba.com/stats/internationalbroadcasterschedule?LeagueID=00&Season=" + NBASchedURLPart2 string = "&RegionID=1&EST=Y" + + Unknown = "Unknown" +) + +// Custom type for GameStatus +type GameStatus int + +// Constants for GameStatus values +const ( + Scheduled GameStatus = 1 + InProgress GameStatus = 2 + Finished GameStatus = 3 + NotFound GameStatus = 4 +) + +// Int() method for GameStatus to get its integer representation +// This is a helpful method if people want to come up with their own templates +func (gs GameStatus) Int() int { + return int(gs) +} + +func (gs GameStatus) Valid() bool { + return gs == Scheduled || gs == InProgress || gs == Finished +} + +func (gs GameStatus) String() string { + switch gs { + case Scheduled: + return "Scheduled" + case InProgress: + return "In Progress" + case Finished: + return "Finished" + case NotFound: + return "Not Found" + default: + return Unknown + } +} + +// All of the structs needed to retrieve data from the live score endpoint +type ScoreboardResponse struct { + Scoreboard Scoreboard `json:"scoreboard"` +} + +type Scoreboard struct { + GameDate string `json:"gameDate"` + Games []Game `json:"games"` +} + +type Game struct { + GameStatus int `json:"gameStatus"` + GameStatusText string `json:"gameStatusText"` + GameTimeUTC string `json:"gameTimeUTC"` + HomeTeam Team `json:"homeTeam"` + AwayTeam Team `json:"awayTeam"` +} + +type Team struct { + TeamTricode string `json:"teamTricode"` + Wins int `json:"wins"` + Losses int `json:"losses"` + Score int `json:"score"` +} + +// All the structs needed to get data from the schedule endpoint +type ScheduleResponse struct { + ResultSets []ResultSet `json:"resultSets"` +} + +type ResultSet struct { + CompleteGameList []ScheduledGame `json:"CompleteGameList,omitempty"` +} + +type ScheduledGame struct { + VtAbbreviation string `json:"vtAbbreviation"` + HtAbbreviation string `json:"htAbbreviation"` + Date string `json:"date"` + Time string `json:"time"` +} + +func (nba *Nba) Template() string { + return " \U000F0806 {{ .HomeTeam}}{{ if .HasStats }} ({{.HomeTeamWins}}-{{.HomeTeamLosses}}){{ end }}{{ if .Started }}:{{.HomeScore}}{{ end }} vs {{ .AwayTeam}}{{ if .HasStats }} ({{.AwayTeamWins}}-{{.AwayTeamLosses}}){{ end }}{{ if .Started }}:{{.AwayScore}}{{ end }} | {{ if not .Started }}{{.GameDate}} | {{ end }}{{.Time}} " //nolint:lll +} + +func (nba *Nba) Enabled() bool { + data, err := nba.getResult() + if err != nil || !data.GameStatus.Valid() { + return false + } + + nba.NBAData = *data + + return true +} + +// Returns an empty Game Data struct with the GameStatus set to NotFound +// Helpful for caching the fact that a game was not found for a team +func (nba *Nba) getGameNotFoundData() string { + return `{ + "HomeTeam":"", + "AwayTeam":"", + "Time":"", + "GameDate":"", + "StartTimeUTC":"", + "GameStatus":4, + "HomeScore":0, + "AwayScore":0, + "HomeTeamWins":0, + "HomeTeamLosses":0, + "AwayTeamWins":0, + "AwayTeamLosses":0 + }` +} + +// parses through a set of games from the score endpoint and looks for props.team in away or home team +func (nba *Nba) findGameScoreByTeamTricode(games []Game, teamTricode string) (*Game, error) { + for _, game := range games { + if game.HomeTeam.TeamTricode == teamTricode || game.AwayTeam.TeamTricode == teamTricode { + return &game, nil + } + } + + return nil, errors.New("no game score found for team") +} + +// parses through a set of games from the schedule endpoint and looks for props.team in away or home team +func (nba *Nba) findGameSchedulebyTeamTricode(games []ScheduledGame, teamTricode string) (*ScheduledGame, error) { + for _, game := range games { + if game.VtAbbreviation == teamTricode || game.HtAbbreviation == teamTricode { + return &game, nil + } + } + + return nil, errors.New("no scheduled game found for team") +} + +// parses the time and date from the schedule endpoint into a UTC time +func (nba *Nba) parseTimetoUTC(timeEST, date string) string { + combinedTime := date + " " + timeEST + timeUTC, err := time.Parse("01/02/2006 03:04 PM", combinedTime) + if err != nil { + return "" + } + + return timeUTC.UTC().Format("2006-01-02T15:04:05Z") +} + +// retrieves data from the score endpoint +func (nba *Nba) retrieveScoreData(teamName string, httpTimeout int) (*NBAData, error) { + body, err := nba.env.HTTPRequest(NBAScoreURL, nil, httpTimeout) + if err != nil { + return nil, err + } + + var scoreboardResponse *ScoreboardResponse + err = json.Unmarshal(body, &scoreboardResponse) + if err != nil { + return nil, err + } + + gameInfo, err := nba.findGameScoreByTeamTricode(scoreboardResponse.Scoreboard.Games, teamName) + if err != nil { + return nil, err + } + + return &NBAData{ + AwayTeam: gameInfo.AwayTeam.TeamTricode, + HomeTeam: gameInfo.HomeTeam.TeamTricode, + Time: gameInfo.GameStatusText, + GameDate: scoreboardResponse.Scoreboard.GameDate, + StartTimeUTC: gameInfo.GameTimeUTC, + GameStatus: GameStatus(gameInfo.GameStatus), + HomeScore: gameInfo.HomeTeam.Score, + AwayScore: gameInfo.AwayTeam.Score, + HomeTeamWins: gameInfo.HomeTeam.Wins, + HomeTeamLosses: gameInfo.HomeTeam.Losses, + AwayTeamWins: gameInfo.AwayTeam.Wins, + AwayTeamLosses: gameInfo.AwayTeam.Losses, + }, nil +} + +// Retrieves the data from the schedule endpoint +func (nba *Nba) retrieveScheduleData(teamName string, httpTimeout int) (*NBAData, error) { + // How many days into the future should we look for a game. + numDaysToSearch := nba.props.GetInt(DaysOffset, 8) + nbaSeason := nba.props.GetString(NBASeason, "2023") + // Get the current date in America/New_York + t := time.Now().In(time.FixedZone("America/New_York", -5*60*60)) + + // Check to see if a game is scheduled while the numDaysToSearch is greater than 0 + for numDaysToSearch > 0 { + // convert t into the format that the API expects "MM/YYYY/DD" + dateStr := fmt.Sprintf("%02d/%02d/%d", t.Month(), t.Day(), t.Year()) + urlEndpoint := NBASchedURLPart1 + nbaSeason + "&Date=" + dateStr + NBASchedURLPart2 + body, err := nba.env.HTTPRequest(urlEndpoint, nil, httpTimeout) + if err != nil { + return nil, err + } + + var scheduleResponse *ScheduleResponse + err = json.Unmarshal(body, &scheduleResponse) + if err != nil { + return nil, err + } + + // Check if we can find a game for the team + gameInfo, err := nba.findGameSchedulebyTeamTricode(scheduleResponse.ResultSets[1].CompleteGameList, teamName) + if err != nil { + // We didn't find a game for the team on this day, so we need to check the next day + t = t.AddDate(0, 0, 1) + numDaysToSearch-- + continue + } + + return &NBAData{ + AwayTeam: gameInfo.VtAbbreviation, + HomeTeam: gameInfo.HtAbbreviation, + Time: gameInfo.Time + " ET", + GameDate: gameInfo.Date, + StartTimeUTC: nba.parseTimetoUTC(gameInfo.Time, gameInfo.Date), + GameStatus: Scheduled, + HomeScore: 0, + AwayScore: 0, + HomeTeamWins: 0, + HomeTeamLosses: 0, + AwayTeamWins: 0, + AwayTeamLosses: 0, + }, nil + } + + return nil, errors.New("no scheduled game found for team within DaysOffset days") +} + +// First try to get the data from the score endpoint, if that fails, try the schedule endpoint +// The score endpoint usually goes live within 12 hours of a game starting +func (nba *Nba) getAvailableGameData(teamName string, httpTimeout int) (*NBAData, error) { + // Get the info from the score endpoint + data, err := nba.retrieveScoreData(teamName, httpTimeout) + if err == nil { + return data, nil + } + + // If the score endpoint doesn't have anything get data from the schedule endpoint + data, err = nba.retrieveScheduleData(teamName, httpTimeout) + if err == nil { + return data, nil + } + + return nil, err +} + +// Gets the data from the cache if it exists +func (nba *Nba) getCacheValue(key string) (*NBAData, error) { + if val, found := nba.env.Cache().Get(key); found { + var nbaData *NBAData + err := json.Unmarshal([]byte(val), &nbaData) + if err != nil { + return nil, err + } + return nbaData, nil + } + + return nil, errors.New("no data in cache") +} + +// Gets the data from the cache for a scheduled game if it exists +// Checks whether the game should have started and if so, removes the cache entry +func (nba *Nba) getCachedScheduleValue(key string) (*NBAData, error) { + data, err := nba.getCacheValue(key) + if err != nil { + return nil, errors.New("no data in cache") + } + + // check if the game was previously not found and we should wait to check again + if data.GameStatus == NotFound { + return data, nil + } + + // check if the current time is after the start time of the game + // if so, we need to refresh the data + startTime, err := time.Parse("2006-01-02T15:04:05Z", data.StartTimeUTC) + if err != nil { + return nil, err + } + + if time.Now().UTC().After(startTime) { + // remove the cache entry + nba.env.Cache().Delete(key) + return nil, errors.New("game has already started") + } + + return data, nil +} + +func (nba *Nba) getResult() (*NBAData, error) { + teamName := nba.props.GetString(TeamName, "") + + cachedScheduleKey := fmt.Sprintf("%s%s", teamName, "schedule") + cachedScoreKey := fmt.Sprintf("%s%s", teamName, "score") + + httpTimeout := nba.props.GetInt(properties.HTTPTimeout, properties.DefaultHTTPTimeout) + + // How often you want to query the API to get live score information, defaults to 2 minutes + cacheScoreTimeout := nba.props.GetInt(properties.CacheTimeout, 2) + + // Cache the schedule information for a day so we don't call the API too often + cacheScheduleTimeout := nba.props.GetInt(properties.CacheTimeout, 1440) + + // Cache the fact a game was not found for 30 minutes so we don't call the API too often + cacheNotFoundTimeout := nba.props.GetInt(properties.CacheTimeout, 30) + + nba.env.Debug("Validating cache data for " + teamName) + + if cacheScheduleTimeout > 0 { + if data, err := nba.getCachedScheduleValue(cachedScheduleKey); err == nil { + return data, nil + } + } + + if cacheScoreTimeout > 0 { + if data, err := nba.getCacheValue(cachedScoreKey); err == nil { + return data, nil + } + } + + nba.env.Debug("Fetching available data for " + teamName) + + data, err := nba.getAvailableGameData(teamName, httpTimeout) + if err != nil { + // cache the fact that we didn't find a game yet for the day for 30m so we don't continuously ping the endpoints + nba.env.Cache().Set(cachedScheduleKey, nba.getGameNotFoundData(), cacheNotFoundTimeout) + nba.env.Error(errors.Join(err, fmt.Errorf("unable to get data for team %s", teamName))) + return nil, err + } + + if !data.GameStatus.Valid() { + err := fmt.Errorf("%d is not a valid game status", data.GameStatus) + nba.env.Error(err) + return nil, err + } + + if cacheScheduleTimeout > 0 && data.GameStatus == Scheduled { + // persist data for team in cache + cachedData, _ := json.Marshal(data) + nba.env.Cache().Set(cachedScheduleKey, string(cachedData), cacheScheduleTimeout) + } + + // if the game is in progress or finished, we can cache the score + if cacheScoreTimeout > 0 && data.GameStatus == InProgress || data.GameStatus == Finished { + // persist data for team in cache + cachedData, _ := json.Marshal(data) + nba.env.Cache().Set(cachedScoreKey, string(cachedData), cacheScoreTimeout) + } + + return data, nil +} + +func (nba *Nba) Init(props properties.Properties, env platform.Environment) { + nba.props = props + nba.env = env +} diff --git a/src/segments/nba_test.go b/src/segments/nba_test.go new file mode 100644 index 00000000..dc1e32dd --- /dev/null +++ b/src/segments/nba_test.go @@ -0,0 +1,122 @@ +package segments + +import ( + "fmt" + "os" + "testing" + "time" + + "github.com/jandedobbeleer/oh-my-posh/src/mock" + "github.com/jandedobbeleer/oh-my-posh/src/properties" + + "github.com/stretchr/testify/assert" + mock2 "github.com/stretchr/testify/mock" +) + +func getTestData(file string) string { + content, _ := os.ReadFile(fmt.Sprintf("../test/%s", file)) + return string(content) +} + +// create Test segment for NBA segment +func TestNBASegment(t *testing.T) { + jsonScheduleData := getTestData("nba/schedule.json") + jsonScoreData := getTestData("nba/score.json") + + cases := []struct { + Case string + JSONResponse string + ExpectedString string + ExpectedEnabled bool + CacheTimeout int + CacheFoundFail bool + TeamName string + DaysOffset int + Error error + }{ + { + Case: "Team (Home Team) Scheduled Game", + JSONResponse: jsonScheduleData, + TeamName: "LAL", + ExpectedString: "󰠆 LAL vs PHX | 10/26/2023 | 10:00 PM ET", + ExpectedEnabled: true, + DaysOffset: 8, + }, + { + Case: "Team (Away Team) Scheduled Game", + JSONResponse: jsonScheduleData, + TeamName: "PHX", + ExpectedString: "󰠆 LAL vs PHX | 10/26/2023 | 10:00 PM ET", + DaysOffset: 4, + ExpectedEnabled: true, + }, + { + Case: "Team (Home Team) Live Game", + JSONResponse: jsonScoreData, + TeamName: "CHA", + ExpectedString: "󰠆 CHA (1-0):13 vs BOS (0-1):8 | Q1 8:23", + ExpectedEnabled: true, + }, + { + Case: "Team (Away Team) Live Game", + JSONResponse: jsonScoreData, + TeamName: "BOS", + ExpectedString: "󰠆 CHA (1-0):13 vs BOS (0-1):8 | Q1 8:23", + ExpectedEnabled: true, + }, + { + Case: "Team not Found", + JSONResponse: jsonScheduleData, + DaysOffset: 8, + TeamName: "INVALID", + ExpectedEnabled: false, + }, + } + + for _, tc := range cases { + env := &mock.MockedEnvironment{} + props := properties.Map{ + properties.CacheTimeout: tc.CacheTimeout, + TeamName: tc.TeamName, + DaysOffset: tc.DaysOffset, + } + + env.On("Error", mock2.Anything) + env.On("Debug", mock2.Anything) + env.On("HTTPRequest", NBAScoreURL).Return([]byte(tc.JSONResponse), tc.Error) + + // Add all the daysOffset to the http request responses + for i := 0; i < tc.DaysOffset; i++ { + currTime := time.Now().In(time.FixedZone("America/New_York", -5*60*60)) + // add offset days to currTime so we can query for games in the future + currTime = currTime.AddDate(0, 0, i) + dateStr := fmt.Sprintf("%02d/%02d/%d", currTime.Month(), currTime.Day(), currTime.Year()) + nbaSeason := fmt.Sprintf("%d", currTime.Year()) + scheduleURLEndpoint := NBASchedURLPart1 + nbaSeason + "&Date=" + dateStr + NBASchedURLPart2 + env.On("HTTPRequest", scheduleURLEndpoint).Return([]byte(tc.JSONResponse), tc.Error) + } + + nba := &Nba{ + props: props, + env: env, + } + + cachedScheduleKey := fmt.Sprintf("%s%s", tc.TeamName, "schedule") + cachedScoreKey := fmt.Sprintf("%s%s", tc.TeamName, "score") + + cache := &mock.MockedCache{} + cache.On("Get", cachedScheduleKey).Return(nba.getGameNotFoundData(), tc.CacheFoundFail) + cache.On("Get", cachedScoreKey).Return(nba.getGameNotFoundData(), tc.CacheFoundFail) + cache.On("Set", cachedScheduleKey, nba.getGameNotFoundData(), tc.CacheTimeout).Return() + cache.On("Set", cachedScoreKey, nba.getGameNotFoundData(), tc.CacheTimeout).Return() + env.On("Cache").Return(cache) + + enabled := nba.Enabled() + assert.Equal(t, tc.ExpectedEnabled, enabled, tc.Case) + if !enabled { + continue + } + + assert.Equal(t, tc.ExpectedString, renderTemplate(env, nba.Template(), nba), tc.Case) + } +} diff --git a/src/segments/umbraco.go b/src/segments/umbraco.go index 73189578..42c592ca 100644 --- a/src/segments/umbraco.go +++ b/src/segments/umbraco.go @@ -158,7 +158,7 @@ func (u *Umbraco) TryFindLegacyUmbraco(configPath string) bool { u.Modern = false if len(appSetting.Value) == 0 { - u.Version = "Unknown" + u.Version = Unknown } else { u.Version = appSetting.Value } diff --git a/src/test/nba/schedule.json b/src/test/nba/schedule.json new file mode 100644 index 00000000..ce788e3f --- /dev/null +++ b/src/test/nba/schedule.json @@ -0,0 +1,96 @@ +{ + "resource": "internationalbroadcasterschedule", + "parameters": { + "LeagueID": "00", + "Season": "2023", + "RegionID": 1, + "Date": "10/26/2023", + "EST": "Y" + }, + "resultSets": [ + { + "NextGameList": [ + { + "gameID": "0022300075", + "vtCity": "Philadelphia", + "vtNickName": "76ers", + "vtShortName": "Philadelphia", + "vtAbbreviation": "PHI", + "htCity": "Milwaukee", + "htNickName": "Bucks", + "htShortName": "Milwaukee", + "htAbbreviation": "MIL", + "date": "10/26/2023", + "time": "07:30 PM", + "day": "Thu", + "broadcasters": [ + { + "broadcastID": "10", + "broadcasterName": "TNT", + "tapeDelayComments": "" + } + ] + }, + { + "gameID": "0022300076", + "vtCity": "Phoenix", + "vtNickName": "Suns", + "vtShortName": "Phoenix", + "vtAbbreviation": "PHX", + "htCity": "Los Angeles", + "htNickName": "Lakers", + "htShortName": "L.A. Lakers", + "htAbbreviation": "LAL", + "date": "10/26/2023", + "time": "10:00 PM", + "day": "Thu", + "broadcasters": [ + { + "broadcastID": "10", + "broadcasterName": "TNT", + "tapeDelayComments": "" + } + ] + } + ] + }, + { + "CompleteGameList": [ + { + "gameID": "0022300075", + "vtCity": "Philadelphia", + "vtNickName": "76ers", + "vtShortName": "Philadelphia", + "vtAbbreviation": "PHI", + "htCity": "Milwaukee", + "htNickName": "Bucks", + "htShortName": "Milwaukee", + "htAbbreviation": "MIL", + "date": "10/26/2023", + "time": "07:30 PM", + "day": "Thu", + "broadcastID": "10", + "broadcasterName": "TNT", + "tapeDelayComments": "" + }, + { + "gameID": "0022300076", + "vtCity": "Phoenix", + "vtNickName": "Suns", + "vtShortName": "Phoenix", + "vtAbbreviation": "PHX", + "htCity": "Los Angeles", + "htNickName": "Lakers", + "htShortName": "L.A. Lakers", + "htAbbreviation": "LAL", + "date": "10/26/2023", + "time": "10:00 PM", + "day": "Thu", + "broadcastID": "10", + "broadcasterName": "TNT", + "tapeDelayComments": "" + } + ] + } + ] +} diff --git a/src/test/nba/score.json b/src/test/nba/score.json new file mode 100644 index 00000000..28559ab2 --- /dev/null +++ b/src/test/nba/score.json @@ -0,0 +1,699 @@ +{ + "meta": { + "version": 1, + "request": "https://nba-prod-us-east-1-mediaops-stats.s3.amazonaws.com/NBA/liveData/scoreboard/todaysScoreboard_00.json", + "time": "2023-10-19 01:17:57.1757", + "code": 200 + }, + "scoreboard": { + "gameDate": "2023-10-19", + "leagueId": "00", + "leagueName": "National Basketball Association", + "games": [ + { + "gameId": "0012300060", + "gameCode": "20231019/BOSCHA", + "gameStatus": 2, + "gameStatusText": "Q1 8:23", + "period": 0, + "gameClock": "Q1 8:23", + "gameTimeUTC": "2023-10-19T23:00:00Z", + "gameEt": "2023-10-19T19:00:00Z", + "regulationPeriods": 4, + "ifNecessary": false, + "seriesGameNumber": "", + "seriesText": "Preseason", + "seriesConference": "", + "poRoundDesc": "", + "gameSubtype": "", + "homeTeam": { + "teamId": 1610612766, + "teamName": "Hornets", + "teamCity": "Charlotte", + "teamTricode": "CHA", + "wins": 1, + "losses": 0, + "score": 13, + "seed": null, + "inBonus": null, + "timeoutsRemaining": 0, + "periods": [ + { + "period": 1, + "periodType": "REGULAR", + "score": 0 + }, + { + "period": 2, + "periodType": "REGULAR", + "score": 0 + }, + { + "period": 3, + "periodType": "REGULAR", + "score": 0 + }, + { + "period": 4, + "periodType": "REGULAR", + "score": 0 + } + ] + }, + "awayTeam": { + "teamId": 1610612738, + "teamName": "Celtics", + "teamCity": "Boston", + "teamTricode": "BOS", + "wins": 0, + "losses": 1, + "score": 8, + "seed": null, + "inBonus": null, + "timeoutsRemaining": 0, + "periods": [ + { + "period": 1, + "periodType": "REGULAR", + "score": 0 + }, + { + "period": 2, + "periodType": "REGULAR", + "score": 0 + }, + { + "period": 3, + "periodType": "REGULAR", + "score": 0 + }, + { + "period": 4, + "periodType": "REGULAR", + "score": 0 + } + ] + }, + "gameLeaders": { + "homeLeaders": { + "personId": 0, + "name": "", + "jerseyNum": "", + "position": "", + "teamTricode": "CHA", + "playerSlug": null, + "points": 0, + "rebounds": 0, + "assists": 0 + }, + "awayLeaders": { + "personId": 0, + "name": "", + "jerseyNum": "", + "position": "", + "teamTricode": "BOS", + "playerSlug": null, + "points": 0, + "rebounds": 0, + "assists": 0 + } + }, + "pbOdds": { + "team": null, + "odds": 0.0, + "suspended": 0 + } + }, + { + "gameId": "0012300061", + "gameCode": "20231019/MINCHI", + "gameStatus": 1, + "gameStatusText": "8:00 pm ET", + "period": 0, + "gameClock": "", + "gameTimeUTC": "2023-10-20T00:00:00Z", + "gameEt": "2023-10-19T20:00:00Z", + "regulationPeriods": 4, + "ifNecessary": false, + "seriesGameNumber": "", + "seriesText": "Preseason", + "seriesConference": "", + "poRoundDesc": "", + "gameSubtype": "", + "homeTeam": { + "teamId": 1610612741, + "teamName": "Bulls", + "teamCity": "Chicago", + "teamTricode": "CHI", + "wins": 0, + "losses": 0, + "score": 0, + "seed": null, + "inBonus": null, + "timeoutsRemaining": 0, + "periods": [ + { + "period": 1, + "periodType": "REGULAR", + "score": 0 + }, + { + "period": 2, + "periodType": "REGULAR", + "score": 0 + }, + { + "period": 3, + "periodType": "REGULAR", + "score": 0 + }, + { + "period": 4, + "periodType": "REGULAR", + "score": 0 + } + ] + }, + "awayTeam": { + "teamId": 1610612750, + "teamName": "Timberwolves", + "teamCity": "Minnesota", + "teamTricode": "MIN", + "wins": 0, + "losses": 0, + "score": 0, + "seed": null, + "inBonus": null, + "timeoutsRemaining": 0, + "periods": [ + { + "period": 1, + "periodType": "REGULAR", + "score": 0 + }, + { + "period": 2, + "periodType": "REGULAR", + "score": 0 + }, + { + "period": 3, + "periodType": "REGULAR", + "score": 0 + }, + { + "period": 4, + "periodType": "REGULAR", + "score": 0 + } + ] + }, + "gameLeaders": { + "homeLeaders": { + "personId": 0, + "name": "", + "jerseyNum": "", + "position": "", + "teamTricode": "CHI", + "playerSlug": null, + "points": 0, + "rebounds": 0, + "assists": 0 + }, + "awayLeaders": { + "personId": 0, + "name": "", + "jerseyNum": "", + "position": "", + "teamTricode": "MIN", + "playerSlug": null, + "points": 0, + "rebounds": 0, + "assists": 0 + } + }, + "pbOdds": { + "team": null, + "odds": 0.0, + "suspended": 0 + } + }, + { + "gameId": "0012300062", + "gameCode": "20231019/DETOKC", + "gameStatus": 1, + "gameStatusText": "8:00 pm ET", + "period": 0, + "gameClock": "", + "gameTimeUTC": "2023-10-20T00:00:00Z", + "gameEt": "2023-10-19T20:00:00Z", + "regulationPeriods": 4, + "ifNecessary": false, + "seriesGameNumber": "", + "seriesText": "Preseason", + "seriesConference": "", + "poRoundDesc": "", + "gameSubtype": "", + "homeTeam": { + "teamId": 1610612760, + "teamName": "Thunder", + "teamCity": "Oklahoma City", + "teamTricode": "OKC", + "wins": 0, + "losses": 0, + "score": 0, + "seed": null, + "inBonus": null, + "timeoutsRemaining": 0, + "periods": [ + { + "period": 1, + "periodType": "REGULAR", + "score": 0 + }, + { + "period": 2, + "periodType": "REGULAR", + "score": 0 + }, + { + "period": 3, + "periodType": "REGULAR", + "score": 0 + }, + { + "period": 4, + "periodType": "REGULAR", + "score": 0 + } + ] + }, + "awayTeam": { + "teamId": 1610612765, + "teamName": "Pistons", + "teamCity": "Detroit", + "teamTricode": "DET", + "wins": 0, + "losses": 0, + "score": 0, + "seed": null, + "inBonus": null, + "timeoutsRemaining": 0, + "periods": [ + { + "period": 1, + "periodType": "REGULAR", + "score": 0 + }, + { + "period": 2, + "periodType": "REGULAR", + "score": 0 + }, + { + "period": 3, + "periodType": "REGULAR", + "score": 0 + }, + { + "period": 4, + "periodType": "REGULAR", + "score": 0 + } + ] + }, + "gameLeaders": { + "homeLeaders": { + "personId": 0, + "name": "", + "jerseyNum": "", + "position": "", + "teamTricode": "OKC", + "playerSlug": null, + "points": 0, + "rebounds": 0, + "assists": 0 + }, + "awayLeaders": { + "personId": 0, + "name": "", + "jerseyNum": "", + "position": "", + "teamTricode": "DET", + "playerSlug": null, + "points": 0, + "rebounds": 0, + "assists": 0 + } + }, + "pbOdds": { + "team": null, + "odds": 0.0, + "suspended": 0 + } + }, + { + "gameId": "0012300063", + "gameCode": "20231019/PHXLAL", + "gameStatus": 1, + "gameStatusText": "10:00 pm ET", + "period": 0, + "gameClock": "", + "gameTimeUTC": "2023-10-20T02:00:00Z", + "gameEt": "2023-10-19T22:00:00Z", + "regulationPeriods": 4, + "ifNecessary": false, + "seriesGameNumber": "", + "seriesText": "Preseason", + "seriesConference": "", + "poRoundDesc": "", + "gameSubtype": "", + "homeTeam": { + "teamId": 1610612747, + "teamName": "Lakers", + "teamCity": "Los Angeles", + "teamTricode": "LAL", + "wins": 0, + "losses": 0, + "score": 0, + "seed": null, + "inBonus": null, + "timeoutsRemaining": 0, + "periods": [ + { + "period": 1, + "periodType": "REGULAR", + "score": 0 + }, + { + "period": 2, + "periodType": "REGULAR", + "score": 0 + }, + { + "period": 3, + "periodType": "REGULAR", + "score": 0 + }, + { + "period": 4, + "periodType": "REGULAR", + "score": 0 + } + ] + }, + "awayTeam": { + "teamId": 1610612756, + "teamName": "Suns", + "teamCity": "Phoenix", + "teamTricode": "PHX", + "wins": 0, + "losses": 0, + "score": 0, + "seed": null, + "inBonus": null, + "timeoutsRemaining": 0, + "periods": [ + { + "period": 1, + "periodType": "REGULAR", + "score": 0 + }, + { + "period": 2, + "periodType": "REGULAR", + "score": 0 + }, + { + "period": 3, + "periodType": "REGULAR", + "score": 0 + }, + { + "period": 4, + "periodType": "REGULAR", + "score": 0 + } + ] + }, + "gameLeaders": { + "homeLeaders": { + "personId": 0, + "name": "", + "jerseyNum": "", + "position": "", + "teamTricode": "LAL", + "playerSlug": null, + "points": 0, + "rebounds": 0, + "assists": 0 + }, + "awayLeaders": { + "personId": 0, + "name": "", + "jerseyNum": "", + "position": "", + "teamTricode": "PHX", + "playerSlug": null, + "points": 0, + "rebounds": 0, + "assists": 0 + } + }, + "pbOdds": { + "team": null, + "odds": 0.0, + "suspended": 0 + } + }, + { + "gameId": "0012300064", + "gameCode": "20231019/DENLAC", + "gameStatus": 1, + "gameStatusText": "10:00 pm ET", + "period": 0, + "gameClock": "", + "gameTimeUTC": "2023-10-20T02:00:00Z", + "gameEt": "2023-10-19T22:00:00Z", + "regulationPeriods": 4, + "ifNecessary": false, + "seriesGameNumber": "", + "seriesText": "Preseason", + "seriesConference": "", + "poRoundDesc": "", + "gameSubtype": "", + "homeTeam": { + "teamId": 1610612746, + "teamName": "Clippers", + "teamCity": "LA", + "teamTricode": "LAC", + "wins": 0, + "losses": 0, + "score": 0, + "seed": null, + "inBonus": null, + "timeoutsRemaining": 0, + "periods": [ + { + "period": 1, + "periodType": "REGULAR", + "score": 0 + }, + { + "period": 2, + "periodType": "REGULAR", + "score": 0 + }, + { + "period": 3, + "periodType": "REGULAR", + "score": 0 + }, + { + "period": 4, + "periodType": "REGULAR", + "score": 0 + } + ] + }, + "awayTeam": { + "teamId": 1610612743, + "teamName": "Nuggets", + "teamCity": "Denver", + "teamTricode": "DEN", + "wins": 0, + "losses": 0, + "score": 0, + "seed": null, + "inBonus": null, + "timeoutsRemaining": 0, + "periods": [ + { + "period": 1, + "periodType": "REGULAR", + "score": 0 + }, + { + "period": 2, + "periodType": "REGULAR", + "score": 0 + }, + { + "period": 3, + "periodType": "REGULAR", + "score": 0 + }, + { + "period": 4, + "periodType": "REGULAR", + "score": 0 + } + ] + }, + "gameLeaders": { + "homeLeaders": { + "personId": 0, + "name": "", + "jerseyNum": "", + "position": "", + "teamTricode": "LAC", + "playerSlug": null, + "points": 0, + "rebounds": 0, + "assists": 0 + }, + "awayLeaders": { + "personId": 0, + "name": "", + "jerseyNum": "", + "position": "", + "teamTricode": "DEN", + "playerSlug": null, + "points": 0, + "rebounds": 0, + "assists": 0 + } + }, + "pbOdds": { + "team": null, + "odds": 0.0, + "suspended": 0 + } + }, + { + "gameId": "0012300065", + "gameCode": "20231019/UTASAC", + "gameStatus": 1, + "gameStatusText": "10:00 pm ET", + "period": 0, + "gameClock": "", + "gameTimeUTC": "2023-10-20T02:00:00Z", + "gameEt": "2023-10-19T22:00:00Z", + "regulationPeriods": 4, + "ifNecessary": false, + "seriesGameNumber": "", + "seriesText": "Preseason", + "seriesConference": "", + "poRoundDesc": "", + "gameSubtype": "", + "homeTeam": { + "teamId": 1610612758, + "teamName": "Kings", + "teamCity": "Sacramento", + "teamTricode": "SAC", + "wins": 0, + "losses": 0, + "score": 0, + "seed": null, + "inBonus": null, + "timeoutsRemaining": 0, + "periods": [ + { + "period": 1, + "periodType": "REGULAR", + "score": 0 + }, + { + "period": 2, + "periodType": "REGULAR", + "score": 0 + }, + { + "period": 3, + "periodType": "REGULAR", + "score": 0 + }, + { + "period": 4, + "periodType": "REGULAR", + "score": 0 + } + ] + }, + "awayTeam": { + "teamId": 1610612762, + "teamName": "Jazz", + "teamCity": "Utah", + "teamTricode": "UTA", + "wins": 0, + "losses": 0, + "score": 0, + "seed": null, + "inBonus": null, + "timeoutsRemaining": 0, + "periods": [ + { + "period": 1, + "periodType": "REGULAR", + "score": 0 + }, + { + "period": 2, + "periodType": "REGULAR", + "score": 0 + }, + { + "period": 3, + "periodType": "REGULAR", + "score": 0 + }, + { + "period": 4, + "periodType": "REGULAR", + "score": 0 + } + ] + }, + "gameLeaders": { + "homeLeaders": { + "personId": 0, + "name": "", + "jerseyNum": "", + "position": "", + "teamTricode": "SAC", + "playerSlug": null, + "points": 0, + "rebounds": 0, + "assists": 0 + }, + "awayLeaders": { + "personId": 0, + "name": "", + "jerseyNum": "", + "position": "", + "teamTricode": "UTA", + "playerSlug": null, + "points": 0, + "rebounds": 0, + "assists": 0 + } + }, + "pbOdds": { + "team": null, + "odds": 0.0, + "suspended": 0 + } + } + ] + } +} diff --git a/website/docs/segments/nba.mdx b/website/docs/segments/nba.mdx new file mode 100644 index 00000000..49affc8d --- /dev/null +++ b/website/docs/segments/nba.mdx @@ -0,0 +1,85 @@ +--- +id: nba +title: NBA +sidebar_label: NBA +--- + +## What + +The NBA segment allows you to display the scheduling and score information for your +favorite NBA team! + +## Sample Configuration + +In order to use the NBA segment, you need to provide a valid team +[tri-code](https://liaison.reuters.com/tools/sports-team-codes) that you'd +like to get data for inside of the configuration. For example, if you'd like +to get information for the Los Angeles Lakers, you'd need to use the "LAL" +tri-code. + +This example uses "LAL" to get information for the Los Angeles Lakers. It also +sets the foreground and background colors to match the theming for the team. +If you are interested in getting information about specific foreground and +background colors you could use for other teams, you can explore some of +the color schemes [here](https://teamcolorcodes.com/nba-team-color-codes/). + +It is recommended that you set the HTTP timeout to a higher value than the +normal default in case it takes some time to gather the scoreboard information. +In this case we have the http_timeout set to 1500. + +import Config from "@site/src/components/Config.js"; + + + +## Properties + +| Name | Type | Description | +| -------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------- | +| `team` | `string` | tri-code for the NBA team you want to get data for | +| `days_offset` | `int` | how many days in advance you wish to see that information for, defaults to 8 | +| `http_timeout` | `int` | How long do you want to wait before you want to see your prompt more than your sugar? I figure a half second is a good default - defaults to 500ms | + +## Template ([info][templates]) + +:::note default template + +```template +\U000F0806 {{ .HomeTeam}}{{ if .HasStats }} ({{.HomeTeamWins}}-{{.HomeTeamLosses}}){{ end }}{{ if .Started }}:{{.HomeScore}}{{ end }} vs {{ .AwayTeam}}{{ if .HasStats }} ({{.AwayTeamWins}}-{{.AwayTeamLosses}}){{ end }}{{ if .Started }}:{{.AwayScore}}{{ end }} | {{ if not .Started }}{{.GameDate}} | {{ end }}{{.Time}} +``` + +::: + +### Properties + +| Name | Type | Description | +| --------------- | --------- | ----------------------------------------------------------- | +| .HomeTeam | `string` | home team for the upcoming game | +| .AwayTeam | `string` | away team for the upcoming game | +| .Time | `string` | time (EST) that the upcoming game will start | +| .GameDate | `string` | date the game will happen | +| .StartTimeUTC | `string` | time (UTC) the game will start | +| .GameStatus | `integer` | integer, 1 = scheduled, 2 = in progress, 3 = finished | +| .HomeScore | `int` | score of the home team | +| .AwayScore | `int` | score of the away team | +| .HomeTeamWins | `int` | number of wins the home team currently has for the season | +| .HomeTeamLosses | `int` | number of losses the home team currently has for the season | +| .AwayTeamWins | `int` | number of wins the away team currently has for the season | +| .AwayTeamLosses | `int` | number of losses the away team currently has for the season | +| .Started | `boolean` | if the game was started or not | +| .HasStats | `boolean` | if the game has game stats or not | + +[templates]: /docs/configuration/templates +[nf-search]: https://www.nerdfonts.com/cheat-sheet