From ecaad893baf2d85caf0bfa6bc4bb356538e82bba Mon Sep 17 00:00:00 2001 From: Asher Samuel Date: Sat, 17 May 2025 19:31:44 +0300 Subject: [PATCH] darts support --- go.mod | 2 ++ go.sum | 2 ++ internal/domain/result.go | 29 ++++++++++++++++ internal/domain/sportmarket.go | 30 +++++++++++++++++ internal/services/result/eval.go | 24 +++++++++++++ internal/services/result/service.go | 52 +++++++++++++++++++++++++++++ makefile | 2 +- 7 files changed, 140 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index a510af6..f4a2e2f 100644 --- a/go.mod +++ b/go.mod @@ -18,6 +18,8 @@ require ( golang.org/x/crypto v0.36.0 ) +require github.com/gorilla/websocket v1.5.3 // indirect + require ( github.com/KyleBanks/depth v1.2.1 // indirect github.com/andybalholm/brotli v1.1.1 // indirect diff --git a/go.sum b/go.sum index 9e77972..d99da5d 100644 --- a/go.sum +++ b/go.sum @@ -63,6 +63,8 @@ github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVI github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= +github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= diff --git a/internal/domain/result.go b/internal/domain/result.go index def8719..aa00779 100644 --- a/internal/domain/result.go +++ b/internal/domain/result.go @@ -249,6 +249,35 @@ type VolleyballResultResponse struct { Bet365ID string `json:"bet365_id"` } +type DartsResultResponse struct { + ID string `json:"id"` + SportID string `json:"sport_id"` + Time string `json:"time"` + TimeStatus string `json:"time_status"` + League struct { + ID string `json:"id"` + Name string `json:"name"` + CC string `json:"cc"` + } `json:"league"` + Home struct { + ID string `json:"id"` + Name string `json:"name"` + ImageID string `json:"image_id"` + CC string `json:"cc"` + } `json:"home"` + Away struct { + ID string `json:"id"` + Name string `json:"name"` + ImageID string `json:"image_id"` + CC string `json:"cc"` + } `json:"away"` + SS string `json:"ss"` + InplayCreatedAt string `json:"inplay_created_at"` + InplayUpdatedAt string `json:"inplay_updated_at"` + ConfirmedAt string `json:"confirmed_at"` + Bet365ID string `json:"bet365_id"` +} + type Score struct { Home string `json:"home"` Away string `json:"away"` diff --git a/internal/domain/sportmarket.go b/internal/domain/sportmarket.go index d540c98..7cdfa61 100644 --- a/internal/domain/sportmarket.go +++ b/internal/domain/sportmarket.go @@ -125,6 +125,23 @@ const ( VOLLEYBALL_SET_ONE_TOTAL_ODD_EVEN VolleyballMarket = 910218 ) +type DartsMarket int64 + +const ( + // Main + DARTS_MATCH_WINNER DartsMarket = 703 // match_winner + DARTS_MATCH_DOUBLE DartsMarket = 150228 // match_double + DARTS_MATCH_TREBLE DartsMarket = 150230 // match_treble + DARTS_CORRECT_LEG_SCORE DartsMarket = 150015 // correct_leg_score + DARTS_TOTAL_LEGS DartsMarket = 150117 // total_legs + + DARTS_MOST_HUNDERED_EIGHTYS DartsMarket = 150030 // "most_180s" + DARTS_TOTAL_HUNDERED_EIGHTYS DartsMarket = 150012 // total_180s + DARTS_MOST_HUNDERED_EIGHTYS_HANDICAP DartsMarket = 150227 // most_180s_handicap + DARTS_PLAYER_HUNDERED_EIGHTYS DartsMarket = 150121 // player_180s + DARTS_FIRST_DART DartsMarket = 150125 // first_dart +) + // TODO: Move this into the database so that it can be modified dynamically var SupportedMarkets = map[int64]bool{ @@ -221,4 +238,17 @@ var SupportedMarkets = map[int64]bool{ int64(VOLLEYBALL_SET_ONE_LINES): false, int64(VOLLEYBALL_SET_ONE_TO_GO_TO_EXTRA_POINTS): false, int64(VOLLEYBALL_SET_ONE_TOTAL_ODD_EVEN): false, + + // Darts Markets + int64(DARTS_MATCH_WINNER): true, + int64(DARTS_TOTAL_LEGS): true, + int64(DARTS_CORRECT_LEG_SCORE): false, + int64(DARTS_MATCH_DOUBLE): false, + int64(DARTS_MATCH_TREBLE): false, + + int64(DARTS_MOST_HUNDERED_EIGHTYS): false, + int64(DARTS_TOTAL_HUNDERED_EIGHTYS): false, + int64(DARTS_MOST_HUNDERED_EIGHTYS_HANDICAP): false, + int64(DARTS_PLAYER_HUNDERED_EIGHTYS): false, + int64(DARTS_FIRST_DART): false, } diff --git a/internal/services/result/eval.go b/internal/services/result/eval.go index 6f36324..6874036 100644 --- a/internal/services/result/eval.go +++ b/internal/services/result/eval.go @@ -297,6 +297,30 @@ func evaluateTotalOverUnder(outcome domain.BetOutcome, score struct{ Home, Away return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("invalid odd header: %s", outcome.OddHeader) } +func evaluateTotalLegs(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) { + total, err := strconv.ParseFloat(outcome.OddName, 64) + if err != nil { + return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("invalid : %s", outcome.OddName) + } + + totalLegs := float64(score.Home + score.Away) + + switch outcome.OddHeader { + case "Over": + if totalLegs > total { + return domain.OUTCOME_STATUS_WIN, nil + } + return domain.OUTCOME_STATUS_LOSS, nil + case "Under": + if totalLegs < total { + return domain.OUTCOME_STATUS_WIN, nil + } + return domain.OUTCOME_STATUS_LOSS, nil + } + + return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("invalid odd header: %s", outcome.OddHeader) +} + func evaluateResultAndTotal(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) { // The handicap will be in the format "U {float}" or "O {float}" diff --git a/internal/services/result/service.go b/internal/services/result/service.go index e5d54ae..d75e585 100644 --- a/internal/services/result/service.go +++ b/internal/services/result/service.go @@ -233,6 +233,12 @@ func (s *Service) FetchResult(ctx context.Context, eventID, oddID, marketID, spo s.logger.Error("Failed to parse cricket", "event id", eventID, "market_id", marketID, "error", err) return domain.CreateResult{}, err } + case domain.DARTS: + result, err = s.parseDarts(resultResp.Results[0], eventID, oddID, marketID, outcome) + if err != nil { + s.logger.Error("Failed to parse cricket", "event id", eventID, "market_id", marketID, "error", err) + return domain.CreateResult{}, err + } default: s.logger.Error("Unsupported sport", "sport", sportID) return domain.CreateResult{}, fmt.Errorf("unsupported sport: %v", sportID) @@ -387,6 +393,34 @@ func (s *Service) parseVolleyball(response json.RawMessage, eventID, oddID, mark } +func (s *Service) parseDarts(response json.RawMessage, eventID, oddID, marketID int64, outcome domain.BetOutcome) (domain.CreateResult, error) { + var dartsRes domain.DartsResultResponse + + if err := json.Unmarshal(response, &dartsRes); err != nil { + } + if dartsRes.TimeStatus != "3" { + s.logger.Warn("Match not yet completed", "event_id", eventID) + return domain.CreateResult{}, fmt.Errorf("match not yet completed") + } + + // result for dart is limited + // only ss is given, format with 2-4 + status, err := s.evaluateDartsOutcome(outcome, dartsRes) + if err != nil { + s.logger.Error("Failed to evaluate outcome", "event_id", eventID, "market_id", marketID, "error", err) + return domain.CreateResult{}, err + } + + return domain.CreateResult{ + BetOutcomeID: 0, + EventID: eventID, + OddID: oddID, + MarketID: marketID, + Status: status, + }, nil + +} + func parseScore(home string, away string) struct{ Home, Away int } { homeVal, _ := strconv.Atoi(strings.TrimSpace(home)) awaVal, _ := strconv.Atoi(strings.TrimSpace(away)) @@ -605,3 +639,21 @@ func (s *Service) evaluateVolleyballOutcome(outcome domain.BetOutcome, res domai return domain.OUTCOME_STATUS_PENDING, nil } + +func (s *Service) evaluateDartsOutcome(outcome domain.BetOutcome, res domain.DartsResultResponse) (domain.OutcomeStatus, error) { + if !domain.SupportedMarkets[outcome.MarketID] { + s.logger.Warn("Unsupported market type", "market_name", outcome.MarketName) + return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("unsupported market type: %s", outcome.MarketName) + } + + score := parseSS(res.SS) + + switch outcome.MarketID { + case int64(domain.DARTS_MATCH_WINNER): + return evaluateFullTimeResult(outcome, score) + case int64(domain.DARTS_TOTAL_LEGS): + return evaluateTotalLegs(outcome, score) + } + + return domain.OUTCOME_STATUS_PENDING, nil +} diff --git a/makefile b/makefile index e949a0a..34afecd 100644 --- a/makefile +++ b/makefile @@ -43,7 +43,7 @@ migrations/up: .PHONY: postgres postgres: @echo 'Running postgres db...' - docker compose -f compose.db.yaml exec postgres psql -U root -d gh + docker compose -f docker-compose.yml exec postgres psql -U root -d gh .PHONY: swagger swagger: