commit 269c635f988396878fcf816c55b585cc3e7392d7 Author: gari Date: Sat Jan 29 19:25:03 2022 +0100 initial commit diff --git a/.envrc b/.envrc new file mode 100644 index 0000000..8392d15 --- /dev/null +++ b/.envrc @@ -0,0 +1 @@ +use flake \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..fd9abdf --- /dev/null +++ b/.gitignore @@ -0,0 +1,95 @@ +#nix flake build output +result/ + +*.log + +### JetBrains template +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/**/usage.statistics.xml +.idea/**/dictionaries +.idea/**/shelf + +# Generated files +.idea/**/contentModel.xml + +# Sensitive or high-churn files +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml +.idea/**/dbnavigator.xml + +# Gradle +.idea/**/gradle.xml +.idea/**/libraries + +# Gradle and Maven with auto-import +# When using Gradle or Maven with auto-import, you should exclude module files, +# since they will be recreated, and may cause churn. Uncomment if using +# auto-import. +# .idea/artifacts +# .idea/compiler.xml +# .idea/jarRepositories.xml +# .idea/modules.xml +# .idea/*.iml +# .idea/modules +# *.iml +# *.ipr + +# CMake +cmake-build-*/ + +# Mongo Explorer plugin +.idea/**/mongoSettings.xml + +# File-based project format +*.iws + +# IntelliJ +out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Cursive Clojure plugin +.idea/replstate.xml + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +# Editor-based Rest Client +.idea/httpRequests + +# Android studio 3.1+ serialized cache file +.idea/caches/build_file_checksums.ser + +### Go template +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib + +# Test binary, built with `go test -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out + +# Dependency directories (remove the comment below to include it) +# vendor/ + diff --git a/Readme.md b/Readme.md new file mode 100644 index 0000000..8956959 --- /dev/null +++ b/Readme.md @@ -0,0 +1,3 @@ +# Berkutschi Ski Jumping Data Scraper + +This is very much WIP \ No newline at end of file diff --git a/berkutschi/http.go b/berkutschi/http.go new file mode 100644 index 0000000..9bb4ad0 --- /dev/null +++ b/berkutschi/http.go @@ -0,0 +1,59 @@ +package berkutschi + +import ( + "context" + "fmt" + "time" + + "github.com/monaco-io/request" + "github.com/rs/zerolog/log" +) + +var pollURL = "https://live.berkutschi.com/events/" + +func Poll(event int) (PollData, error) { + ctx, _ := context.WithTimeout(context.Background(), 15*time.Second) + c := request.Client{ + Context: ctx, + URL: fmt.Sprintf("%s%d.json", pollURL, event), + Method: "GET", + } + data := new(PollData) + resp := c.Send().Scan(data) + if !resp.OK() { + return PollData{}, resp.Error() + } + return *data, nil +} + +func (b *Berkutschi) registerClient() error { + var body = struct { + Channel string `json:"channel"` + ID string `json:"id"` + SupportedConnectionTypes []string `json:"supportedConnectionTypes"` + Version string `json:"version"` + }{ + Channel: "/meta/handshake", + Version: "1.0", + ID: "1", + SupportedConnectionTypes: []string{"websocket"}, + } + var result []BerkutschiClientRegisterResponse + + c := request.Client{ + URL: "https://live.berkutschi.com/faye", + Method: "POST", + JSON: body, + } + resp := c.Send().Scan(&result) + if !resp.OK() { + // handle error + log.Error().Err(fmt.Errorf("%v", resp.Error())).Fields(struct{ Event int }{Event: b.event}).Send() + return resp.Error() + } + + b.log.Debug().Msgf("%+v", result) + b.clientID = result[0].ClientID + //TODO check length of result + return nil +} diff --git a/berkutschi/pollType.go b/berkutschi/pollType.go new file mode 100644 index 0000000..2f93588 --- /dev/null +++ b/berkutschi/pollType.go @@ -0,0 +1,246 @@ +package berkutschi + +type PollData struct { + Data struct { + AvailableStates []string `json:"available_states"` + Current struct { + Bib string `json:"bib"` + Club string `json:"club"` + Cumul struct { + Points float64 `json:"points"` + Rank int64 `json:"rank"` + } `json:"cumul"` + DateOfBirth string `json:"date_of_birth"` + Dnf bool `json:"dnf"` + DNS bool `json:"dns"` + Dq bool `json:"dq"` + Dqp bool `json:"dqp"` + Dtb1 interface{} `json:"dtb1"` + Dtb2 interface{} `json:"dtb2"` + Dtb3 interface{} `json:"dtb3"` + Firstname string `json:"firstname"` + Fiscode string `json:"fiscode"` + Gatecomp float64 `json:"gatecomp"` + Gatesnumber interface{} `json:"gatesnumber"` + Image string `json:"image"` + Image2 string `json:"image2"` + Image3 string `json:"image3"` + Judge struct { + One struct { + Discard bool `json:"discard"` + ID string `json:"id"` + Order int64 `json:"order"` + Rate float64 `json:"rate"` + } `json:"1"` + Two struct { + Discard bool `json:"discard"` + ID string `json:"id"` + Order int64 `json:"order"` + Rate float64 `json:"rate"` + } `json:"2"` + Three struct { + ID string `json:"id"` + Order int64 `json:"order"` + Rate float64 `json:"rate"` + } `json:"3"` + Four struct { + ID string `json:"id"` + Order int64 `json:"order"` + Rate float64 `json:"rate"` + } `json:"4"` + Five struct { + ID string `json:"id"` + Order int64 `json:"order"` + Rate float64 `json:"rate"` + } `json:"5"` + } `json:"judge"` + Judgetotal struct { + Points float64 `json:"points"` + Rank int64 `json:"rank"` + } `json:"judgetotal"` + Lastname string `json:"lastname"` + Length struct { + Length float64 `json:"length"` + Points float64 `json:"points"` + } `json:"length"` + Nat string `json:"nat"` + Nps bool `json:"nps"` + Points struct { + Points float64 `json:"points"` + Rank int64 `json:"rank"` + } `json:"points"` + Speed struct { + Speed string `json:"speed"` + } `json:"speed"` + Wind struct { + Compensation float64 `json:"compensation"` + Wind float64 `json:"wind"` + } `json:"wind"` + } `json:"current"` + Messages []struct { + Text string `json:"text"` + Timestamp string `json:"timestamp"` + } `json:"messages"` + Next struct { + Bib string `json:"bib"` + Club string `json:"club"` + DateOfBirth string `json:"date_of_birth"` + Firstname string `json:"firstname"` + Fiscode string `json:"fiscode"` + Image string `json:"image"` + Image2 string `json:"image2"` + Image3 string `json:"image3"` + Lastname string `json:"lastname"` + Nat string `json:"nat"` + } `json:"next"` + Raceinfo struct { + Discipline interface{} `json:"discipline"` + Event string `json:"event"` + Gender string `json:"gender"` + Judges struct { + One struct { + Nation string `json:"nation"` + } `json:"1"` + Two struct { + Nation string `json:"nation"` + } `json:"2"` + Three struct { + Nation string `json:"nation"` + } `json:"3"` + Four struct { + Nation string `json:"nation"` + } `json:"4"` + Five struct { + Nation string `json:"nation"` + } `json:"5"` + } `json:"judges"` + Mvalue string `json:"mvalue"` + No string `json:"no"` + Team string `json:"team"` + } `json:"raceinfo"` + Results []struct { + Bib int64 `json:"bib"` + DNS bool `json:"dns"` + Dq bool `json:"dq"` + FinalRank int64 `json:"final_rank"` + Gatecomp float64 `json:"gatecomp"` + Gatesnumber interface{} `json:"gatesnumber"` + Length1 float64 `json:"length1"` + LengthPoints1 float64 `json:"length_points1"` + Name string `json:"name"` + Nation string `json:"nation"` + Nps bool `json:"nps"` + Points1 float64 `json:"points1"` + Q bool `json:"q"` + Qualified string `json:"qualified"` + Speed string `json:"speed"` + Total float64 `json:"total"` + Wind struct { + Compensation float64 `json:"compensation"` + Wind float64 `json:"wind"` + } `json:"wind"` + } `json:"results"` + Startlist struct { + Jumpers []struct { + Bib string `json:"bib"` + Club string `json:"club"` + DateOfBirth string `json:"date_of_birth"` + Firstname string `json:"firstname"` + Fiscode string `json:"fiscode"` + Image string `json:"image"` + Image2 string `json:"image2"` + Image3 string `json:"image3"` + Lastname string `json:"lastname"` + Nat string `json:"nat"` + } `json:"jumpers"` + Runno string `json:"runno"` + } `json:"startlist"` + Status string `json:"status"` + Team bool `json:"team"` + } `json:"data"` + Event struct { + Canceled bool `json:"canceled"` + Cancelled bool `json:"cancelled"` + CompetitionActs []struct { + Date string `json:"date"` + Sort string `json:"sort"` + Time string `json:"time"` + } `json:"competition_acts"` + CreatedAt string `json:"created_at"` + Date string `json:"date"` + EndOfPeriod bool `json:"end_of_period"` + Fiscodex string `json:"fiscodex"` + Gender string `json:"gender"` + Hill struct { + BackwindFactor string `json:"backwind_factor"` + BuildingYear int64 `json:"building_year"` + Certificate string `json:"certificate"` + Contact string `json:"contact"` + CreatedAt interface{} `json:"created_at"` + Description string `json:"description"` + GateFactor string `json:"gate_factor"` + HeadwindFactor string `json:"headwind_factor"` + Height interface{} `json:"height"` + HillImages []struct { + HillThumb string `json:"hill_thumb"` + HillThumb150 string `json:"hill_thumb150"` + HillThumb200 string `json:"hill_thumb200"` + HillThumb480 string `json:"hill_thumb480"` + PhotoFileName string `json:"photo_file_name"` + } `json:"hill_images"` + HillJumpers []struct { + Distance string `json:"distance"` + HillID int64 `json:"hill_id"` + ID int64 `json:"id"` + Jumper struct { + Name string `json:"name"` + Nation struct { + FlagPath string `json:"flag_path"` + ID int64 `json:"id"` + Name string `json:"name"` + Shortname string `json:"shortname"` + Slug string `json:"slug"` + Visible bool `json:"visible"` + } `json:"nation"` + } `json:"jumper"` + JumperID int64 `json:"jumper_id"` + RecordDate string `json:"record_date"` + } `json:"hill_jumpers"` + HillRecord string `json:"hill_record"` + HillSize string `json:"hill_size"` + ID int64 `json:"id"` + InrunLength string `json:"inrun_length"` + Location struct { + Lat string `json:"lat"` + Lng string `json:"lng"` + Name string `json:"name"` + Nation struct { + FlagPath string `json:"flag_path"` + Shortname string `json:"shortname"` + } `json:"nation"` + } `json:"location"` + LocationID int64 `json:"location_id"` + Name string `json:"name"` + OutrunGradient string `json:"outrun_gradient"` + PointK string `json:"point_k"` + Size string `json:"size"` + Slug string `json:"slug"` + Speed string `json:"speed"` + StandCapacity int64 `json:"stand_capacity"` + TableGradient string `json:"table_gradient"` + TableHeight string `json:"table_height"` + TowerHeight interface{} `json:"tower_height"` + UpdatedAt string `json:"updated_at"` + Visible bool `json:"visible"` + Windfinder string `json:"windfinder"` + } `json:"hill"` + HillID int64 `json:"hill_id"` + ID int64 `json:"id"` + Remarks string `json:"remarks"` + Season string `json:"season"` + Sorts []string `json:"sorts"` + Start string `json:"start"` + Team int64 `json:"team"` + UpdatedAt string `json:"updated_at"` + } `json:"event"` +} diff --git a/berkutschi/types.go b/berkutschi/types.go new file mode 100644 index 0000000..99008e2 --- /dev/null +++ b/berkutschi/types.go @@ -0,0 +1,35 @@ +package berkutschi + +type BerkutschiClientRegisterResponse struct { + Advice struct { + Interval int64 `json:"interval"` + Reconnect string `json:"reconnect"` + Timeout int64 `json:"timeout"` + } `json:"advice"` + Channel string `json:"channel"` + ClientID string `json:"clientId"` + ID string `json:"id"` + Successful bool `json:"successful"` + SupportedConnectionTypes []string `json:"supportedConnectionTypes"` + Version string `json:"version"` +} + +type BerkutschiClientMessages interface { + Marshal() ([]byte, error) +} + +type BerkutschiConnectMessages []BerkutschiConnectMessage +type BerkutschiConnectMessage struct { + Channel string `json:"channel"` + ClientID string `json:"clientId"` + ConnectionType string `json:"connectionType"` + ID string `json:"id"` +} + +type BerkutschiSubscribeMessages []BerkutschiSubscribeMessage +type BerkutschiSubscribeMessage struct { + Channel string `json:"channel"` + ClientID string `json:"clientId"` + ID string `json:"id"` + Subscription string `json:"subscription"` +} diff --git a/berkutschi/websocket.go b/berkutschi/websocket.go new file mode 100644 index 0000000..74c8bd2 --- /dev/null +++ b/berkutschi/websocket.go @@ -0,0 +1,129 @@ +package berkutschi + +import ( + "context" + "encoding/json" + "fmt" + "net/url" + "time" + + "github.com/rs/zerolog" + "github.com/rs/zerolog/log" + "nhooyr.io/websocket" +) + +var u = url.URL{Scheme: "wss", Host: "live.berkutschi.com", Path: "/faye"} + +type Berkutschi struct { + conn *websocket.Conn + event int + clientID string + log zerolog.Logger + ctx context.Context + TX chan BerkutschiClientMessages + RX chan []byte +} + +func Init(event int) *Berkutschi { + l := log.With().Int("event", event).Logger() + b := &Berkutschi{ + event: event, + TX: make(chan BerkutschiClientMessages), + RX: make(chan []byte), + log: l, + } + b.registerClient() + b.connect() + b.connectAndSubscribe() + + return b +} + +func (b *Berkutschi) connectAndSubscribe() { + connectMessage := BerkutschiConnectMessage{ + Channel: "/meta/connect", + ClientID: b.clientID, + ConnectionType: "websocket", + ID: "2", + } + subscribeMessage := BerkutschiSubscribeMessage{ + Channel: "/meta/subscribe", + ClientID: b.clientID, + Subscription: fmt.Sprintf("/messages/%d", b.event), + ID: "3", + } + b.TX <- BerkutschiConnectMessages{connectMessage} + b.log.Debug().Msgf("connected to berkutschi") + b.TX <- BerkutschiSubscribeMessages{subscribeMessage} + b.log.Debug().Msgf("subscribed to event %d", b.event) +} + +func (b *Berkutschi) connect() error { + ctx, _ := context.WithTimeout(context.Background(), time.Second*10) + c, _, err := websocket.Dial(ctx, u.String(), nil) + if err != nil { + b.log.Err(fmt.Errorf("Error connecting to websocket: %s", err)).Send() + } + b.log.Debug().Msgf("Connected to websocket") + ctx, cancel := context.WithCancel(context.Background()) + go func() { + defer cancel() + for { + select { + case <-ctx.Done(): + return + default: + _, message, err := c.Read(ctx) + if err != nil { + b.log.Error().Err(fmt.Errorf("Error reading from websocket, reconnecting: %s", err)).Send() + return + } + b.RX <- message + } + } + }() + + go func() { + defer cancel() + for { + select { + case <-ctx.Done(): + return + case message := <-b.TX: + byteMessage, _ := message.Marshal() + err := c.Write(ctx, websocket.MessageText, byteMessage) + if err != nil { + b.log.Error().Err(fmt.Errorf("Error writing to websocket, reconnecting: %s", err)).Send() + return + } + b.log.Debug().Msgf("Sent message: %v", message) + } + } + }() + + go func() { + <-ctx.Done() + go b.closeAndReconnect() + }() + + b.conn = c + + return err +} + +func (b *Berkutschi) closeAndReconnect() error { + b.registerClient() + b.log.Debug().Msgf("Reconnecting websocket connection") + b.conn.Close(websocket.StatusInternalError, "") + b.connect() + b.connectAndSubscribe() + return nil +} + +func (m BerkutschiConnectMessages) Marshal() ([]byte, error) { + return json.Marshal(m) +} + +func (m BerkutschiSubscribeMessages) Marshal() ([]byte, error) { + return json.Marshal(m) +} diff --git a/default.nix b/default.nix new file mode 100644 index 0000000..8978c53 --- /dev/null +++ b/default.nix @@ -0,0 +1,7 @@ +{ pkgs ? import { } }: +with pkgs; +mkShell { + nativeBuildInputs = [ + go_1_17 + ]; +} \ No newline at end of file diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..6908856 --- /dev/null +++ b/flake.lock @@ -0,0 +1,41 @@ +{ + "nodes": { + "flake-utils": { + "locked": { + "lastModified": 1642700792, + "narHash": "sha256-XqHrk7hFb+zBvRg6Ghl+AZDq03ov6OshJLiSWOoX5es=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "846b2ae0fc4cc943637d3d1def4454213e203cba", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1643119265, + "narHash": "sha256-mmDEctIkHSWcC/HRpeaw6QOe+DbNOSzc0wsXAHOZWwo=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "b05d2077ebe219f6a47825767f8bab5c6211d200", + "type": "github" + }, + "original": { + "id": "nixpkgs", + "type": "indirect" + } + }, + "root": { + "inputs": { + "flake-utils": "flake-utils", + "nixpkgs": "nixpkgs" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..fcadcca --- /dev/null +++ b/flake.nix @@ -0,0 +1,15 @@ +{ + description = "berkutschi"; + + inputs.flake-utils.url = "github:numtide/flake-utils"; + + outputs = { self, nixpkgs, flake-utils }: + flake-utils.lib.eachDefaultSystem + (system: + let pkgs = nixpkgs.legacyPackages.${system}; in + { + devShell = import ./default.nix { inherit pkgs; }; + defaultPackage = import ./package.nix { inherit pkgs;}; + } + ); +} \ No newline at end of file diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..a28e441 --- /dev/null +++ b/go.mod @@ -0,0 +1,18 @@ +module berkutschi + +go 1.17 + +require ( + github.com/gorilla/websocket v1.4.2 + github.com/maurodelazeri/gorilla-reconnect v0.0.0-20180328170005-42501a5438b9 + github.com/mehdioa/nlog v0.0.0-20210327090009-d60bf476a16a + github.com/monaco-io/request v1.0.15 +) + +require ( + github.com/jpillora/backoff v1.0.0 // indirect + github.com/klauspost/compress v1.10.3 // indirect + github.com/rs/zerolog v1.26.1 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect + nhooyr.io/websocket v1.8.7 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..ee8f00e --- /dev/null +++ b/go.sum @@ -0,0 +1,98 @@ +github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= +github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M= +github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= +github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= +github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= +github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI= +github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo= +github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw= +github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM= +github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= +github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/jpillora/backoff v1.0.0 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA= +github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= +github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/klauspost/compress v1.10.3 h1:OP96hzwJVBIHYU52pVTI6CczrxPvrGfgqF9N5eTO0Q8= +github.com/klauspost/compress v1.10.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= +github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/maurodelazeri/gorilla-reconnect v0.0.0-20180328170005-42501a5438b9 h1:ZuBaZYBKi9zJFaqXyw0hN46nvBcU315To+SN+Qh0wjU= +github.com/maurodelazeri/gorilla-reconnect v0.0.0-20180328170005-42501a5438b9/go.mod h1:jawYJmNk6FVmenPRjYlbV+OcQ9fBGqv3dZwZnUSoR48= +github.com/mehdioa/nlog v0.0.0-20210327090009-d60bf476a16a h1:p/otnVjZSfec48nsELzyGDwoAMtD+xoG8a6c5TW8Fuw= +github.com/mehdioa/nlog v0.0.0-20210327090009-d60bf476a16a/go.mod h1:Co+vI+QcF2CG+gvNdG+kvzh2f9EE+YiF0OSdWoAmlOU= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/monaco-io/request v1.0.15 h1:krhHE0yL6yL+xP0YwX5udyd+xnfQfHccop3Nsixec54= +github.com/monaco-io/request v1.0.15/go.mod h1:voq81GC2YDynQM3W/4D6oqGhqb+u7zULvbLzFkTmDfo= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rs/xid v1.3.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= +github.com/rs/zerolog v1.26.1 h1:/ihwxqH+4z8UxyI70wM1z9yCvkWcfz/a3mj48k/Zngc= +github.com/rs/zerolog v1.26.1/go.mod h1:/wSSJWX7lVrsOwlbyTRSOJvqRlc+WjWlfes+CiJ+tmc= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= +github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= +github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20211215165025-cf75a172585e/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +nhooyr.io/websocket v1.8.7 h1:usjR2uOr/zjjkVMy0lW+PPohFok7PCow5sDjLgX4P4g= +nhooyr.io/websocket v1.8.7/go.mod h1:B70DZP8IakI65RVQ51MsWP/8jndNma26DVA/nFSCgW0= diff --git a/main.go b/main.go new file mode 100644 index 0000000..c90948e --- /dev/null +++ b/main.go @@ -0,0 +1,165 @@ +package main + +import ( + "context" + "encoding/json" + "fmt" + "os" + "strings" + "time" + + "github.com/rs/zerolog" + "github.com/rs/zerolog/log" + + "berkutschi/berkutschi" +) + +const event = 3860 +const logFile = "my.log" + +var Judges = [5]JugdeRaceInfo{} + +func main() { + zerolog.TimeFieldFormat = time.RFC1123Z + zerolog.SetGlobalLevel(zerolog.InfoLevel) + f, err := os.OpenFile(logFile, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0664) + consoleWriter := zerolog.ConsoleWriter{Out: os.Stdout, TimeFormat: time.RFC1123Z} + multi := zerolog.MultiLevelWriter(consoleWriter, f) + log.Logger = zerolog.New(multi).With().Timestamp().Caller().Logger() + + pollData, err := berkutschi.Poll(event) + if err != nil { + log.Panic().Err(fmt.Errorf("Error while polling: %s", err)).Send() + } + fillJudgeraceinfo(pollData) + + b := berkutschi.Init(event) + + go func() { + jumperInfo := JumperInfo{} + jumperscore := JumperScore{} + for { + message := <-b.RX + var jumpMessage BerkutschiJumpUpdateMessages + err := json.Unmarshal(message, &jumpMessage) + //log.Debug().Msgf("Received message: %v", string(message)) + if err == nil && jumpMessage[0].Data.Current.Lastname != "" { + jumper, err := fillJumperInfo(jumpMessage) + if err != nil { + continue + } + if newJumperInfoData(jumperInfo, jumper) { + jumperInfo = jumper + log.Info().Msgf("New jumper info: %+v", jumperInfo) + } + score, _ := fillJumperScore(jumpMessage) + if score.Rank != 0 && newJumperScoreData(jumperscore, score) { + jumperscore = score + log.Info().Msgf("New jumper score: %+v", jumperscore) + } + } else if err != nil { + log.Error().RawJSON("receivedMessage", message).Err(fmt.Errorf("%v", err)) + } else { + if jumpMessage[0].Data.Next.Lastname == "" { + log.Debug().RawJSON("receivedMessage", message).Msg("Received message i cant decode") + } + } + } + }() + + ctx, _ := context.WithCancel(context.Background()) + <-ctx.Done() +} + +func fillJumperInfo(m BerkutschiJumpUpdateMessages) (JumperInfo, error) { + var err error + JumperInfo := JumperInfo{} + if m[0].Data.Current.Firstname == "" || m[0].Data.Current.Lastname == "" { + err = fmt.Errorf("Name is empty") + return JumperInfo, err + } + JumperInfo.Name = fmt.Sprint(m[0].Data.Current.Firstname, " ", strings.ToUpper(m[0].Data.Current.Lastname)) + JumperInfo.Bib = m[0].Data.Current.Bib + JumperInfo.Nation = strings.ToUpper(m[0].Data.Current.Nat) + JumperInfo.Image = m[0].Data.Current.Image2 + + return JumperInfo, err +} + +func fillJumperScore(m BerkutschiJumpUpdateMessages) (JumperScore, error) { + var err error + JumperScore := JumperScore{} + if m[0].Data.Current.Firstname == "" || m[0].Data.Current.Lastname == "" { + err = fmt.Errorf("Name is empty") + return JumperScore, err + } + JumperScore.Name = fmt.Sprint(m[0].Data.Current.Firstname, " ", strings.ToUpper(m[0].Data.Current.Lastname)) + JumperScore.Bib = m[0].Data.Current.Bib + JumperScore.Nation = strings.ToUpper(m[0].Data.Current.Nat) + JumperScore.Points = m[0].Data.Current.Cumul.Points + JumperScore.Rank = m[0].Data.Current.Cumul.Rank + JumperScore.Wind = m[0].Data.Current.Wind.Wind + JumperScore.Length = fmt.Sprintf("%vm", m[0].Data.Current.Length.Length) + for i, _ := range JumperScore.Judges { + switch i { + case 0: + JumperScore.Judges[i].Score = m[0].Data.Current.Judge.One.Rate + JumperScore.Judges[i].Nation = Judges[i].Nation + JumperScore.Judges[i].Discard = m[0].Data.Current.Judge.One.Discard + case 1: + JumperScore.Judges[i].Score = m[0].Data.Current.Judge.Two.Rate + JumperScore.Judges[i].Nation = Judges[i].Nation + JumperScore.Judges[i].Discard = m[0].Data.Current.Judge.Two.Discard + case 2: + JumperScore.Judges[i].Score = m[0].Data.Current.Judge.Three.Rate + JumperScore.Judges[i].Nation = Judges[i].Nation + JumperScore.Judges[i].Discard = m[0].Data.Current.Judge.Three.Discard + case 3: + JumperScore.Judges[i].Score = m[0].Data.Current.Judge.Four.Rate + JumperScore.Judges[i].Nation = Judges[i].Nation + JumperScore.Judges[i].Discard = m[0].Data.Current.Judge.Four.Discard + case 4: + JumperScore.Judges[i].Score = m[0].Data.Current.Judge.Five.Rate + JumperScore.Judges[i].Nation = Judges[i].Nation + JumperScore.Judges[i].Discard = m[0].Data.Current.Judge.Five.Discard + } + } + + return JumperScore, err +} + +func fillJudgeraceinfo(data berkutschi.PollData) { + for i, _ := range Judges { + switch i { + case 0: + Judges[i].Nation = strings.ToUpper(data.Data.Raceinfo.Judges.One.Nation) + case 1: + Judges[i].Nation = strings.ToUpper(data.Data.Raceinfo.Judges.Two.Nation) + case 2: + Judges[i].Nation = strings.ToUpper(data.Data.Raceinfo.Judges.Three.Nation) + case 3: + Judges[i].Nation = strings.ToUpper(data.Data.Raceinfo.Judges.Four.Nation) + case 4: + Judges[i].Nation = strings.ToUpper(data.Data.Raceinfo.Judges.Five.Nation) + } + } +} + +func newJumperInfoData(oldData, newData JumperInfo) bool { + if oldData.Name != newData.Name || oldData.Bib != newData.Bib || oldData.Nation != newData.Nation || oldData.Image != newData.Image { + return true + } + return false +} +func newJumperScoreData(oldData, newData JumperScore) bool { + if oldData.Name != newData.Name || oldData.Bib != newData.Bib || oldData.Nation != newData.Nation || + oldData.Points != newData.Points || oldData.Rank != newData.Rank || oldData.Wind != newData.Wind || oldData.Length != newData.Length { + return true + } + for i, oldJudge := range oldData.Judges { + if oldJudge.Score != newData.Judges[i].Score || oldJudge.Nation != newData.Judges[i].Nation || oldJudge.Discard != newData.Judges[i].Discard { + return true + } + } + return false +} diff --git a/package.nix b/package.nix new file mode 100644 index 0000000..fcb7204 --- /dev/null +++ b/package.nix @@ -0,0 +1,30 @@ +{ pkgs ? import { } }: +with pkgs; +let + version = "0.0.1"; + deps = []; + nativeDeps = [ + ]; +in +pkgs.buildGo117Module { + pname = "berkutschi"; + inherit version; + + src = ./.; + + buildInputs = [ + ] ++deps; + nativeBuildInputs = [ + ] ++nativeDeps; + + tags = [ ]; + + allowGoReference = true; + + vendorSha256 = "sha256-qXi32q5PjnvYj6LUUTT9vF3VICj6mFnMUdVA1FJMSD0="; + + meta = { + description = "A Client for the Berkutschi WS Api"; + homepage = "https://git.entr0py.de/garionion/berkutschi"; + }; +} \ No newline at end of file diff --git a/types.go b/types.go new file mode 100644 index 0000000..3624084 --- /dev/null +++ b/types.go @@ -0,0 +1,125 @@ +package main + +type JugdeRaceInfo struct { + Nation string +} + +type JumperInfo struct { + Name string + Bib string + Nation string + Image string +} + +type JumperScore struct { + Name string + Bib string + Nation string + Points float64 + Rank int + Wind float64 + Length string + Judges [5]Judge +} + +type Judge struct { + Nation string + Score float64 + Discard bool +} + +type BerkutschiJumpUpdateMessages []BerkutschiJumpUpdateMessage +type BerkutschiJumpUpdateMessage struct { + Channel string `json:"channel"` + Data struct { + Current struct { + Bib string `json:"bib"` + Club string `json:"club"` + Cumul struct { + Points float64 `json:"points"` + Rank int `json:"rank"` + } `json:"cumul"` + DateOfBirth string `json:"date_of_birth"` + Dnf bool `json:"dnf"` + DNS bool `json:"dns"` + Dq bool `json:"dq"` + Dqp bool `json:"dqp"` + Dtb1 interface{} `json:"dtb1"` + Dtb2 interface{} `json:"dtb2"` + Dtb3 interface{} `json:"dtb3"` + Firstname string `json:"firstname"` + Fiscode string `json:"fiscode"` + Gatecomp float64 `json:"gatecomp"` + Gatesnumber interface{} `json:"gatesnumber"` + Image string `json:"image"` + Image2 string `json:"image2"` + Image3 string `json:"image3"` + Judge struct { + One struct { + Discard bool `json:"discard"` + ID string `json:"id"` + Order int64 `json:"order"` + Rate float64 `json:"rate"` + } `json:"1"` + Two struct { + Discard bool `json:"discard"` + ID string `json:"id"` + Order int64 `json:"order"` + Rate float64 `json:"rate"` + } `json:"2"` + Three struct { + Discard bool `json:"discard"` + ID string `json:"id"` + Order int64 `json:"order"` + Rate float64 `json:"rate"` + } `json:"3"` + Four struct { + Discard bool `json:"discard"` + ID string `json:"id"` + Order int64 `json:"order"` + Rate float64 `json:"rate"` + } `json:"4"` + Five struct { + Discard bool `json:"discard"` + ID string `json:"id"` + Order int64 `json:"order"` + Rate float64 `json:"rate"` + } `json:"5"` + } `json:"judge"` + Judgetotal struct { + Points float64 `json:"points"` + Rank int64 `json:"rank"` + } `json:"judgetotal"` + Lastname string `json:"lastname"` + Length struct { + Length float64 `json:"length"` + Points float64 `json:"points"` + } `json:"length"` + Nat string `json:"nat"` + Nps bool `json:"nps"` + Points struct { + Points float64 `json:"points"` + Rank int `json:"rank"` + } `json:"points"` + Speed struct { + Speed string `json:"speed"` + } `json:"speed"` + Wind struct { + Compensation float64 `json:"compensation"` + Wind float64 `json:"wind"` + } `json:"wind"` + } `json:"current"` + Next struct { + Bib string `json:"bib"` + Club string `json:"club"` + DateOfBirth string `json:"date_of_birth"` + Firstname string `json:"firstname"` + Fiscode string `json:"fiscode"` + Image string `json:"image"` + Image2 string `json:"image2"` + Image3 string `json:"image3"` + Lastname string `json:"lastname"` + Nat string `json:"nat"` + } `json:"next"` + } `json:"data"` +}