/* 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 ( "encoding/json" "fmt" "log" "strconv" "time" "github.com/gorilla/websocket" "github.com/spf13/cobra" "scm.dairydemon.net/filifa/mlblive/cmd/internal/statsapi" ) var gamePk int type Feed struct { MetaData metadata } type metadata struct { TimeStamp string } func patch(feedResp []byte, gamePk, ts, updateId string) ([]byte, error) { diffPatchResp, err := statsapi.RequestDiffPatch(gamePk, ts, updateId) if err != nil { return feedResp, err } patches, err := diffPatchResp.ExtractPatches() if err != nil { return feedResp, err } for _, patch := range patches { feedResp, err = patch.Apply(feedResp) if err != nil { return feedResp, err } } return feedResp, err } func newWebsocket(gamePk string) (*statsapi.GamedayWebsocket, <-chan error, error) { ws, err := statsapi.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(gamePk string) (*statsapi.GamedayWebsocket, []byte, error) { ws, _, err := newWebsocket(gamePk) if err != nil { return nil, nil, err } feedResp, err := statsapi.RequestFeed(gamePk) return ws, feedResp, err } func updateFeed(ws *statsapi.GamedayWebsocket, feedResp []byte, gamePk string) ([]byte, error) { var feed Feed err := json.Unmarshal(feedResp, &feed) if err != nil { return nil, err } ts := feed.MetaData.TimeStamp var p statsapi.Push err = ws.ReadJSON(&p) if websocket.IsUnexpectedCloseError(err, statsapi.GameFinalCode, statsapi.GameUnavailableCode) { log.Println(err) newWs, feedResp, err := handleUnexpectedClose(gamePk) if err != nil { return feedResp, err } *ws = *newWs return feedResp, err } else if err != nil { return nil, err } feedResp, err = patch(feedResp, gamePk, ts, p.UpdateId) if err != nil { feedResp, err = statsapi.RequestFeed(gamePk) } return feedResp, err } func subscribe(cmd *cobra.Command, args []string) { pkStr := strconv.Itoa(gamePk) ws, _, err := newWebsocket(pkStr) if err != nil { log.Fatal(err) } defer ws.Close() feedResp, err := statsapi.RequestFeed(pkStr) if err != nil { log.Fatal(err) } for { fmt.Println(string(feedResp)) feedResp, err = updateFeed(ws, feedResp, pkStr) 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().IntVarP(&gamePk, "gamePk", "g", 0, "game PK") subscribeCmd.MarkFlagRequired("gamePk") }