/* Copyright © 2024 filifa This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ package cmd import ( "context" "encoding/json" "fmt" "log" "time" "github.com/antihax/optional" "github.com/evanphx/json-patch/v5" "github.com/gorilla/websocket" "github.com/spf13/cobra" "scm.dairydemon.net/filifa/mlbstats/api" "scm.dairydemon.net/filifa/mlbstats/api/models" ) var gamePk int32 func extractPatches(resp string) ([]jsonpatch.Patch, error) { var patches []jsonpatch.Patch var objs []map[string]jsonpatch.Patch err := json.Unmarshal([]byte(resp), &objs) if err != nil { return patches, err } for _, obj := range objs { patch := obj["diff"] patches = append(patches, patch) } return patches, err } func patch(feed *models.BaseballGameRestObject, client *api.APIClient) error { opts := api.GameApiLiveGameDiffPatchV1Opts{ StartTimecode: optional.NewString(feed.MetaData.TimeStamp), } diffPatch, _, err := client.GameApi.LiveGameDiffPatchV1(context.Background(), feed.GamePk, &opts) if err != nil { return err } patches, err := extractPatches(diffPatch) if err != nil { return err } enc, err := json.Marshal(feed) if err != nil { return err } for _, patch := range patches { enc, err = patch.Apply(enc) if err != nil { return err } } err = json.Unmarshal(enc, &feed) return err } func newWebsocket(gamePk int32) (*gamedayWebsocket, <-chan error, error) { ws, err := newGamedayWebsocket(gamePk) if err != nil { return nil, nil, err } ch := make(chan error) go ws.keepAlive(10*time.Second, ch) return ws, ch, err } func handleUnexpectedClose(feed *models.BaseballGameRestObject, client *api.APIClient) (*gamedayWebsocket, error) { ws, _, err := newWebsocket(feed.GamePk) if err != nil { return nil, err } newFeed, _, err := client.GameApi.LiveGameV1(context.Background(), feed.GamePk, nil) *feed = newFeed return ws, err } func updateFeed(feed *models.BaseballGameRestObject, ws *gamedayWebsocket, client *api.APIClient) error { var p push err := ws.ReadJSON(&p) if websocket.IsUnexpectedCloseError(err, GameFinalCode, GameUnavailableCode) { log.Println(err) newWs, err := handleUnexpectedClose(feed, client) if err != nil { return err } *ws = *newWs return err } else if err != nil { return err } err = patch(feed, client) if err != nil { newFeed, _, err := client.GameApi.LiveGameV1(context.Background(), feed.GamePk, nil) if err != nil { return err } *feed = newFeed } return err } func subscribe(cmd *cobra.Command, args []string) { ws, _, err := newWebsocket(gamePk) if err != nil { log.Fatal(err) } defer ws.Close() client := api.NewAPIClient(api.NewConfiguration()) feed, _, err := client.GameApi.LiveGameV1(context.Background(), gamePk, nil) if err != nil { log.Fatal(err) } for { json, err := json.Marshal(feed) if err != nil { log.Fatal(err) } fmt.Println(string(json)) err = updateFeed(&feed, ws, client) if err != nil { log.Fatal(err) } } } // subscribeCmd represents the subscribe command var subscribeCmd = &cobra.Command{ Use: "subscribe -g [gamePk]", Short: "Output live game data", Long: `Output live game data. Establish a websocket connection with ws.statsapi.mlb.com and output JSON with live updates of a game.`, Args: cobra.NoArgs, Run: subscribe, } func init() { rootCmd.AddCommand(subscribeCmd) // Here you will define your flags and configuration settings. // Cobra supports Persistent Flags which will work for this command // and all subcommands, e.g.: // subscribeCmd.PersistentFlags().String("foo", "", "A help for foo") // Cobra supports local flags which will only run when this command // is called directly, e.g.: subscribeCmd.Flags().Int32VarP(&gamePk, "gamePk", "g", 0, "game PK") subscribeCmd.MarkFlagRequired("gamePk") }