876 lines
28 KiB
Go
876 lines
28 KiB
Go
|
|
package daemon
|
|||
|
|
|
|||
|
|
import (
|
|||
|
|
"fmt"
|
|||
|
|
"sort"
|
|||
|
|
"strconv"
|
|||
|
|
"strings"
|
|||
|
|
"time"
|
|||
|
|
|
|||
|
|
"gordenko.dev/dima/flashscore"
|
|||
|
|
"gordenko.dev/dima/onexbet"
|
|||
|
|
"gordenko.dev/dima/tipper/model"
|
|||
|
|
"gordenko.dev/dima/web/api"
|
|||
|
|
"gordenko.dev/dima/ws"
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
func (s *Daemon) tryLinkToLiveMatch(sportID int, liveMatch onexbet.LiveMatch) {
|
|||
|
|
matchName := fmt.Sprintf("%s - %s", liveMatch.Home, liveMatch.Away)
|
|||
|
|
|
|||
|
|
s.logger.Printf("try link: %s\n", matchName)
|
|||
|
|
// Если уже распознан - пропускаем
|
|||
|
|
|
|||
|
|
// На всякий пожарный проверка
|
|||
|
|
//_, ok := s.onexbetMatchToTeamMatch[liveMatch.MatchID]
|
|||
|
|
//if ok {
|
|||
|
|
// s.logger.Printf("try link: %s\n", matchName)
|
|||
|
|
// return
|
|||
|
|
//}
|
|||
|
|
|
|||
|
|
// Если этот матч уже проверяли - игнорируем
|
|||
|
|
s.mutex.Lock()
|
|||
|
|
|
|||
|
|
unrecognizedMatches, ok := s.unrecognizedOnexbetMatches[sportID]
|
|||
|
|
if !ok {
|
|||
|
|
s.logger.Printf("unrecognized matches for the sport %d not found\n", sportID)
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if unrecognizedMatches[liveMatch.MatchID] {
|
|||
|
|
//s.logger.Printf("Match (%s) is unrecognized. Don't repeat identification\n", matchName)
|
|||
|
|
s.mutex.Unlock()
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
s.mutex.Unlock()
|
|||
|
|
|
|||
|
|
homeTeamCandidates, err := s.fsmodel.ListTeamCandidates(sportID, liveMatch.Home)
|
|||
|
|
if err != nil {
|
|||
|
|
s.logger.Printf("fsmodel.ListFootballTeamCandidates: %s\n", err)
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
//s.logger.Printf("homeTeamCandidates (for %s): %q", liveMatch.Home, homeTeamCandidates)
|
|||
|
|
|
|||
|
|
awayTeamCandidates, err := s.fsmodel.ListTeamCandidates(sportID, liveMatch.Away)
|
|||
|
|
if err != nil {
|
|||
|
|
s.logger.Printf("fsmodel.ListFootballTeamCandidates: %s\n", err)
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
//s.logger.Printf("awayTeamCandidates (for %s): %q", liveMatch.Away, awayTeamCandidates)
|
|||
|
|
//
|
|||
|
|
|
|||
|
|
teamMatches, ok := s.teamMatches[sportID]
|
|||
|
|
if !ok {
|
|||
|
|
err = fmt.Errorf("Not found matches for the sportID %d", sportID)
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
for _, match := range teamMatches {
|
|||
|
|
var isTeamsSwapped bool
|
|||
|
|
|
|||
|
|
isIdentified := s.tryIdentifyMatch(match.Home.TeamID, match.Away.TeamID,
|
|||
|
|
homeTeamCandidates, awayTeamCandidates)
|
|||
|
|
|
|||
|
|
if !isIdentified {
|
|||
|
|
// Пробуем опознать если home и away поменять местами
|
|||
|
|
isIdentified = s.tryIdentifyMatch(match.Home.TeamID, match.Away.TeamID,
|
|||
|
|
awayTeamCandidates, homeTeamCandidates)
|
|||
|
|
|
|||
|
|
isTeamsSwapped = true
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if isIdentified {
|
|||
|
|
|
|||
|
|
if !match.Home.WasLinked {
|
|||
|
|
err = s.fsmodel.SetTeamWasLinked(match.Home.TeamID)
|
|||
|
|
if err != nil {
|
|||
|
|
s.logger.Printf("fsmodel.SetTeamWasLinked: %s; teamID=%s\n",
|
|||
|
|
err, match.Home.TeamID)
|
|||
|
|
}
|
|||
|
|
s.mutex.Lock()
|
|||
|
|
match.Home.WasLinked = true
|
|||
|
|
s.mutex.Unlock()
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if !match.Away.WasLinked {
|
|||
|
|
err = s.fsmodel.SetTeamWasLinked(match.Away.TeamID)
|
|||
|
|
if err != nil {
|
|||
|
|
s.logger.Printf("fsmodel.SetTeamWasLinked: %s; teamID=%s\n",
|
|||
|
|
err, match.Away.TeamID)
|
|||
|
|
}
|
|||
|
|
s.mutex.Lock()
|
|||
|
|
match.Away.WasLinked = true
|
|||
|
|
s.mutex.Unlock()
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
s.mutex.Lock()
|
|||
|
|
match.IsLinkedToOnexbet = true
|
|||
|
|
match.OnexbetMatchID = liveMatch.MatchID
|
|||
|
|
match.OnexbetStatsMatchID = liveMatch.StatsMatchID
|
|||
|
|
match.Score = liveMatch.Score
|
|||
|
|
match.ScoreByPeriods = liveMatch.ScoreByPeriods
|
|||
|
|
match.CurrentPeriod = liveMatch.CurrentPeriod
|
|||
|
|
match.IsTeamsSwapped = isTeamsSwapped
|
|||
|
|
|
|||
|
|
s.onexbetMatchToTeamMatch[liveMatch.MatchID] = match
|
|||
|
|
s.identifiedMatches[liveMatch.MatchID] = true
|
|||
|
|
|
|||
|
|
s.logger.Printf("Identified: %s; %s; %s\n", matchName, liveMatch.MatchID, match.MatchID)
|
|||
|
|
|
|||
|
|
// Матч опознан и перешел в inplay
|
|||
|
|
s.publisher.Publish(
|
|||
|
|
getChannel(match.SportID),
|
|||
|
|
WSMessageInplayStatusChanged,
|
|||
|
|
InplayStatusMessage{
|
|||
|
|
MatchID: match.MatchID,
|
|||
|
|
IsInplay: match.IsInplay,
|
|||
|
|
IsLinkedToOnexbet: match.IsLinkedToOnexbet,
|
|||
|
|
},
|
|||
|
|
)
|
|||
|
|
s.mutex.Unlock()
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
s.mutex.Lock()
|
|||
|
|
// Кешируем негативный результат, чтобы не искать в базе ежеминутно
|
|||
|
|
// Если пользователь распознает новые матчи, - кэш будет очищен
|
|||
|
|
unrecognizedMatches, ok = s.unrecognizedOnexbetMatches[sportID]
|
|||
|
|
if !ok {
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
unrecognizedMatches[liveMatch.MatchID] = true
|
|||
|
|
s.mutex.Unlock()
|
|||
|
|
|
|||
|
|
s.logger.Printf("Match (%s; %s) is not identified. Added to unrecognized\n", matchName, liveMatch.MatchID)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func (s *Daemon) tryIdentifyMatch(homeTeamID, awayTeamID string, homeTeamCandidates, awayTeamCandidates []string) (_ bool) {
|
|||
|
|
for _, candidateHomeTeamID := range homeTeamCandidates {
|
|||
|
|
if homeTeamID == candidateHomeTeamID {
|
|||
|
|
// Если нашли совпадение для home - ищем совпадение для away
|
|||
|
|
for _, candidateAwayTeamID := range awayTeamCandidates {
|
|||
|
|
if awayTeamID == candidateAwayTeamID {
|
|||
|
|
// Mатч идентифицирован
|
|||
|
|
return true
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func (s *Daemon) onOnexbetWsClientConnected(conn *ws.Conn) {
|
|||
|
|
s.logger.Printf("Onexbet Ws Client Connected\n")
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/*
|
|||
|
|
func (s *Daemon) onOnexbetMatchFinished(conn *ws.Conn, in onexbet.WatchReq) {
|
|||
|
|
s.mutex.Lock()
|
|||
|
|
defer s.mutex.Unlock()
|
|||
|
|
|
|||
|
|
match, ok := s.onexbetMatchToTeamMatch[in.MatchID]
|
|||
|
|
if !ok {
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
match.IsInplay = false
|
|||
|
|
}
|
|||
|
|
*/
|
|||
|
|
|
|||
|
|
func (s *Daemon) onOnexbetTeamLiveMatches(conn *ws.Conn, in onexbet.LiveMatchesMessage) {
|
|||
|
|
//s.logger.Println("onOnexbetTeamLiveMatches")
|
|||
|
|
//s.logger.Printf("onexbetMatchToTeamMatch size: %d\n", len(s.onexbetMatchToTeamMatch))
|
|||
|
|
|
|||
|
|
sportID, ok := onexbetSportToFlashscoreSport[in.SportID]
|
|||
|
|
if !ok {
|
|||
|
|
s.logger.Printf("Bug: unknown OnexbetSport %d\n", in.SportID)
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
s.logger.Printf("Onexbet live matches: %d, watching: %d; Inplay: %d\n",
|
|||
|
|
len(in.Matches), len(in.Watching), len(s.onexbetMatchToTeamMatch))
|
|||
|
|
|
|||
|
|
liveMap := make(map[string]onexbet.LiveMatch)
|
|||
|
|
|
|||
|
|
// В цикле рашем 3 задачи:
|
|||
|
|
// - формируем словарь из live матчей на сайте 1xBet, для удобства дальнейшей обработки
|
|||
|
|
// - для матчей в inplay проверяем не появился ли liveMatch.StatsMatchID, по которому
|
|||
|
|
// можно будет узнать результат матча на сайте 1xBet
|
|||
|
|
// - пытаемся распознать новые матчи
|
|||
|
|
for _, liveMatch := range in.Matches {
|
|||
|
|
liveMap[liveMatch.MatchID] = liveMatch
|
|||
|
|
|
|||
|
|
match, ok := s.onexbetMatchToTeamMatch[liveMatch.MatchID]
|
|||
|
|
if ok {
|
|||
|
|
s.logger.Printf("Match (%s - %s) already identified\n", match.Home.CanonicalName, match.Away.CanonicalName)
|
|||
|
|
// ВАЖНО!
|
|||
|
|
// liveMatch.StatsMatchID появится когда матч перейдет в лайв.
|
|||
|
|
if match.OnexbetStatsMatchID == "" && liveMatch.StatsMatchID != "" {
|
|||
|
|
match.OnexbetStatsMatchID = liveMatch.StatsMatchID
|
|||
|
|
match.Score = liveMatch.Score
|
|||
|
|
match.ScoreByPeriods = liveMatch.ScoreByPeriods
|
|||
|
|
match.CurrentPeriod = liveMatch.CurrentPeriod
|
|||
|
|
}
|
|||
|
|
} else {
|
|||
|
|
if !s.identifiedMatches[liveMatch.MatchID] {
|
|||
|
|
// Матча нет среди распознанных - пытаемся опознать
|
|||
|
|
s.tryLinkToLiveMatch(sportID, liveMatch)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
var watchingMap = make(map[string]bool)
|
|||
|
|
|
|||
|
|
for _, onexbetMatchID := range in.Watching {
|
|||
|
|
watchingMap[onexbetMatchID] = true
|
|||
|
|
|
|||
|
|
_, isMatchInInplay := s.onexbetMatchToTeamMatch[onexbetMatchID]
|
|||
|
|
if !isMatchInInplay {
|
|||
|
|
// Матча больше нет в inplay. Если 1xbet присылает его в Watching -
|
|||
|
|
// значит API-запрос Unwatch где-то потерялся. Поэтому отправляем снова.
|
|||
|
|
s.onexbetAPIClient.Call(api.CallReq{
|
|||
|
|
FuncName: "unwatch",
|
|||
|
|
In: onexbet.WatchReq{
|
|||
|
|
SportID: in.SportID,
|
|||
|
|
MatchID: onexbetMatchID,
|
|||
|
|
},
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Цикл по inplay матчам
|
|||
|
|
//if sportID == flashscore.Tennis {
|
|||
|
|
//
|
|||
|
|
//s.checkInplayTennisMatches(liveMap, watchingMap)
|
|||
|
|
//} else {
|
|||
|
|
s.checkInplayTeamMatches(sportID, in.SportID, liveMap, watchingMap)
|
|||
|
|
//}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
//func (s *Daemon) checkInplayTennisMatches(liveMap map[string]bool, watchingMap map[string]bool) {
|
|||
|
|
//for onexbetMatchID, match := range s.onexbetMatchToTeamMatch {
|
|||
|
|
//if match.
|
|||
|
|
//}
|
|||
|
|
//}
|
|||
|
|
|
|||
|
|
func (s *Daemon) checkInplayTeamMatches(sportID int, onexbetSportID onexbet.OnexbetSport, liveMap map[string]onexbet.LiveMatch, watchingMap map[string]bool) {
|
|||
|
|
for onexbetMatchID, match := range s.onexbetMatchToTeamMatch {
|
|||
|
|
if match.SportID != sportID {
|
|||
|
|
// Если другой спорт - пропускаем матч
|
|||
|
|
continue
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
liveMatch, isIn1xbetLive := liveMap[onexbetMatchID]
|
|||
|
|
|
|||
|
|
if !isIn1xbetLive {
|
|||
|
|
// МАТЧИ могут просто исчезать из ЛАЙВ списка, поэтому просто удалять
|
|||
|
|
// нельзя! Сперва запросим результат матча у 1xbet и решим что делать.
|
|||
|
|
//err := s.processMatchDisappearedFromLive(match, onexbetMatchID)
|
|||
|
|
//if err != nil {
|
|||
|
|
// s.logger.Printf("processMatchDisappearedFromLive: %s\n", err)
|
|||
|
|
//}
|
|||
|
|
|
|||
|
|
s.logger.Printf("Inplay match (%s - %s) not found in 1xbet live matches. Remove from inplay\n",
|
|||
|
|
match.Home.CanonicalName, match.Away.CanonicalName)
|
|||
|
|
|
|||
|
|
s.removeMatchFromInplay(match, onexbetMatchID)
|
|||
|
|
} else {
|
|||
|
|
if !watchingMap[onexbetMatchID] {
|
|||
|
|
// Матч есть в лайве 1xbet (liveMap) - но нет в наблюдаемых. Например,
|
|||
|
|
// перезагрузился 1xbet демон, или отписались.
|
|||
|
|
if len(match.Strategies) > 0 {
|
|||
|
|
// Если еще есть не сработавшие стратегии - начинаем наблюдать.
|
|||
|
|
//s.logger.Printf("Watch: %d, %s\n", in.SportID, onexbetMatchID)
|
|||
|
|
|
|||
|
|
// Подписываемся на матчи, которые только что идентифицировали + на те, которые
|
|||
|
|
// пропали из watch списка (например из-за перезагрузки 1xBET демона)
|
|||
|
|
|
|||
|
|
for _, strategy := range match.Strategies {
|
|||
|
|
if strategy.IsNeedToWatch(liveMatch) {
|
|||
|
|
// Если хотя бы одной стратегии нужно наблюдать за матчем
|
|||
|
|
// - подписываемся и прерываем цикл
|
|||
|
|
match.IsInplay = true
|
|||
|
|
|
|||
|
|
s.onexbetAPIClient.Call(api.CallReq{
|
|||
|
|
FuncName: "watch",
|
|||
|
|
In: onexbet.WatchReq{
|
|||
|
|
SportID: onexbetSportID,
|
|||
|
|
MatchID: onexbetMatchID,
|
|||
|
|
},
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
s.mutex.Lock()
|
|||
|
|
// Матч дабавили в inplay
|
|||
|
|
s.publisher.Publish(
|
|||
|
|
getChannel(match.SportID),
|
|||
|
|
WSMessageInplayStatusChanged,
|
|||
|
|
InplayStatusMessage{
|
|||
|
|
MatchID: match.MatchID,
|
|||
|
|
IsInplay: match.IsInplay,
|
|||
|
|
IsLinkedToOnexbet: match.IsLinkedToOnexbet,
|
|||
|
|
},
|
|||
|
|
)
|
|||
|
|
s.mutex.Unlock()
|
|||
|
|
// ВАЖНО!
|
|||
|
|
break
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
} else {
|
|||
|
|
s.mutex.Lock()
|
|||
|
|
//s.logger.Printf("Unwatch: %d, %s\n", in.SportID, onexbetMatchID)
|
|||
|
|
// Матч больше не интересен
|
|||
|
|
// Все стратегии сработали - отписываемся от матча
|
|||
|
|
delete(s.onexbetMatchToTeamMatch, onexbetMatchID)
|
|||
|
|
match.IsInplay = false
|
|||
|
|
|
|||
|
|
s.onexbetAPIClient.Call(api.CallReq{
|
|||
|
|
FuncName: "unwatch",
|
|||
|
|
In: onexbet.WatchReq{
|
|||
|
|
SportID: onexbetSportID,
|
|||
|
|
MatchID: onexbetMatchID,
|
|||
|
|
},
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
// Матч убрали из inplay
|
|||
|
|
s.publisher.Publish(
|
|||
|
|
getChannel(match.SportID),
|
|||
|
|
WSMessageInplayStatusChanged,
|
|||
|
|
InplayStatusMessage{
|
|||
|
|
MatchID: match.MatchID,
|
|||
|
|
IsInplay: match.IsInplay,
|
|||
|
|
IsLinkedToOnexbet: match.IsLinkedToOnexbet,
|
|||
|
|
},
|
|||
|
|
)
|
|||
|
|
s.mutex.Unlock()
|
|||
|
|
}
|
|||
|
|
} else {
|
|||
|
|
// Матч есть в лайве 1xbet (liveMap) и в наблюдаемых - все OK.
|
|||
|
|
// Ничего не делаем.
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/*
|
|||
|
|
func (s *Daemon) processMatchDisappearedFromLive(match *TeamMatch, onexbetMatchID string) (err error) {
|
|||
|
|
if match.OnexbetStatsMatchID == "" {
|
|||
|
|
// Ситуация возвожна, когда матч появился на короткий срок, OnexbetStatsMatchID еще
|
|||
|
|
// не получил и затем исчез.
|
|||
|
|
err = fmt.Errorf("1xBet match %s has empty OnexbetStatsMatchID", onexbetMatchID)
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
matchResult, err := onexbet.GetTeamMatchResult(match.OnexbetStatsMatchID)
|
|||
|
|
if err != nil {
|
|||
|
|
err = fmt.Errorf("onexbet.GetTeamMatchResult: %s", err)
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if matchResult.Status != onexbet.StatusMatchCompleted {
|
|||
|
|
// Если матч не завершен - пропускаем. Предполагаем что матч просто
|
|||
|
|
// временно исчез из лайва
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
// Матч завершен - загружаем прогнозы, чтобы выставить результаты
|
|||
|
|
tips, err := s.model.ListMatchTips(match.MatchID)
|
|||
|
|
if err != nil {
|
|||
|
|
err = fmt.Errorf("model.ListMatchTips: %s; matchID=%s", err, match.MatchID)
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if len(tips) > 0 {
|
|||
|
|
// На один матч может быть несколько прогнозов, поэтому важно знать что результаты
|
|||
|
|
// всех прогнозов удалось записать в БД. Пока все результаты записать не удастся -
|
|||
|
|
// это метод будет вызыватся снова и снова.
|
|||
|
|
for _, tip := range tips {
|
|||
|
|
err = s.trySetTipResult(tip, matchResult)
|
|||
|
|
if err != nil {
|
|||
|
|
err = fmt.Errorf("trySetTipResult: %s; tipID=%d", err, tip.TipID)
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
// Результат всех прогнозов по матчу успешно записан либо прогнозов не было.
|
|||
|
|
// В любом случае - удаляем матч из лайва.
|
|||
|
|
s.removeMatchFromInplay(match, onexbetMatchID)
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
*/
|
|||
|
|
|
|||
|
|
func (s *Daemon) removeMatchFromInplay(match *TeamMatch, onexbetMatchID string) {
|
|||
|
|
s.mutex.Lock()
|
|||
|
|
defer s.mutex.Unlock()
|
|||
|
|
|
|||
|
|
delete(s.onexbetMatchToTeamMatch, onexbetMatchID)
|
|||
|
|
match.IsInplay = false
|
|||
|
|
|
|||
|
|
// Матч убрали из inplay
|
|||
|
|
s.publisher.Publish(
|
|||
|
|
getChannel(match.SportID),
|
|||
|
|
WSMessageInplayStatusChanged,
|
|||
|
|
InplayStatusMessage{
|
|||
|
|
MatchID: match.MatchID,
|
|||
|
|
IsInplay: match.IsInplay,
|
|||
|
|
IsLinkedToOnexbet: match.IsLinkedToOnexbet,
|
|||
|
|
},
|
|||
|
|
)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
type WhyNot struct {
|
|||
|
|
MatchID string `json:"matchID"`
|
|||
|
|
Comment string `json:"comment"`
|
|||
|
|
Time string `json:"time"`
|
|||
|
|
HomeGoals int `json:"homeGoals"`
|
|||
|
|
AwayGoals int `json:"awayGoals"`
|
|||
|
|
GamePace float64 `json:"gamePace"`
|
|||
|
|
GamePaceFrame int `json:"gamePaceFrame"` // count from Zero
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
//func (s *Daemon) onOnexbetTennisLiveMatchData(conn *ws.Conn, upd onexbet.TeamLiveMatchData) {
|
|||
|
|
// s.mutex.Lock()
|
|||
|
|
// defer s.mutex.Unlock()
|
|||
|
|
|
|||
|
|
// s.logger.Printf("tennis MatchData: %s\n", upd.MatchID)
|
|||
|
|
|
|||
|
|
//}
|
|||
|
|
/*
|
|||
|
|
type TeamLiveMatchData struct {
|
|||
|
|
MatchID string `json:"matchID"`
|
|||
|
|
HomeAttacks int `json:"homeAttacks"`
|
|||
|
|
HomeDangerousAttacks int `json:"homeDangerousAttacks"`
|
|||
|
|
HomeShotsOnTarget int `json:"homeShotsOnTarget"`
|
|||
|
|
HomeShotsOffTarget int `json:"homeShotsOffTarget"`
|
|||
|
|
AwayAttacks int `json:"awayAttacks"`
|
|||
|
|
AwayDangerousAttacks int `json:"awayDangerousAttacks"`
|
|||
|
|
AwayShotsOnTarget int `json:"awayShotsOnTarget"`
|
|||
|
|
AwayShotsOffTarget int `json:"awayShotsOffTarget"`
|
|||
|
|
CurrentTime int `json:"currentTime"`
|
|||
|
|
HomeGoals int `json:"homeGoals"`
|
|||
|
|
AwayGoals int `json:"awayGoals"`
|
|||
|
|
Winner *Winner3Way `json:"winner"`
|
|||
|
|
Total Total `json:"total"`
|
|||
|
|
Handicap Handicap `json:"handicap"`
|
|||
|
|
IndividualTotalHome Total `json:"individualTotalHome"`
|
|||
|
|
IndividualTotalAway Total `json:"individualTotalAway"`
|
|||
|
|
H1 Half `json:"h1"` // 1st half
|
|||
|
|
// tennis
|
|||
|
|
Winner2Way *Winner2Way `json:"winner2way"`
|
|||
|
|
|
|||
|
|
// score
|
|||
|
|
Score Score `json:"score"`
|
|||
|
|
ScoreByPeriods map[int]Score `json:"scoreByPeriods"`
|
|||
|
|
CurrentPeriod int `json:"currentPeriod"`
|
|||
|
|
Periods map[int]Period `json:"periods"`
|
|||
|
|
}
|
|||
|
|
*/
|
|||
|
|
|
|||
|
|
func swapTeamLiveMatchData(d onexbet.TeamLiveMatchData) onexbet.TeamLiveMatchData {
|
|||
|
|
d.AwayAttacks, d.HomeAttacks = d.HomeAttacks, d.AwayAttacks
|
|||
|
|
d.AwayDangerousAttacks, d.HomeDangerousAttacks = d.HomeDangerousAttacks, d.AwayDangerousAttacks
|
|||
|
|
d.AwayShotsOnTarget, d.HomeShotsOnTarget = d.HomeShotsOnTarget, d.AwayShotsOnTarget
|
|||
|
|
d.AwayShotsOffTarget, d.HomeShotsOffTarget = d.HomeShotsOffTarget, d.AwayShotsOffTarget
|
|||
|
|
d.AwayGoals, d.HomeGoals = d.HomeGoals, d.AwayGoals
|
|||
|
|
|
|||
|
|
if d.Winner != nil {
|
|||
|
|
d.Winner.Away, d.Winner.Home = d.Winner.Home, d.Winner.Away
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if d.Winner2Way != nil {
|
|||
|
|
d.Winner2Way.Away, d.Winner2Way.Home = d.Winner2Way.Home, d.Winner2Way.Away
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
d.Handicap.Away, d.Handicap.Home = d.Handicap.Home, d.Handicap.Away
|
|||
|
|
d.IndividualTotalAway, d.IndividualTotalHome = d.IndividualTotalHome, d.IndividualTotalAway
|
|||
|
|
|
|||
|
|
d.H1.IndividualTotalAway, d.H1.IndividualTotalHome = d.H1.IndividualTotalHome, d.H1.IndividualTotalAway
|
|||
|
|
|
|||
|
|
d.Score.Away, d.Score.Home = d.Score.Home, d.Score.Away
|
|||
|
|
|
|||
|
|
for periodNumber, score := range d.ScoreByPeriods {
|
|||
|
|
score.Away, score.Home = score.Home, score.Away
|
|||
|
|
|
|||
|
|
d.ScoreByPeriods[periodNumber] = score
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
for periodNumber, period := range d.Periods {
|
|||
|
|
period.Handicap.Away, period.Handicap.Home = period.Handicap.Home, period.Handicap.Away
|
|||
|
|
|
|||
|
|
d.Periods[periodNumber] = period
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return d
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func (s *Daemon) onOnexbetTeamLiveMatchData(conn *ws.Conn, upd onexbet.TeamLiveMatchData) {
|
|||
|
|
s.mutex.Lock()
|
|||
|
|
defer s.mutex.Unlock()
|
|||
|
|
|
|||
|
|
s.logger.Printf("MatchData: %s\n", upd.MatchID)
|
|||
|
|
|
|||
|
|
// маппинг -
|
|||
|
|
match, ok := s.onexbetMatchToTeamMatch[upd.MatchID]
|
|||
|
|
if !ok {
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if match.IsTeamsSwapped {
|
|||
|
|
upd = swapTeamLiveMatchData(upd)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
//if match.SportID == flashscore.Handball {
|
|||
|
|
//x, _ := json.MarshalIndent(upd, "", " ")
|
|||
|
|
//fmt.Printf("MatchID: %s (1xbet MatchID: %s), Time: %d, HomeGoals: %d, AwayGoals: %d\n",
|
|||
|
|
// match.MatchID, upd.MatchID, upd.CurrentTime, upd.HomeGoals, upd.AwayGoals)
|
|||
|
|
//}
|
|||
|
|
|
|||
|
|
channel := getChannel(match.SportID)
|
|||
|
|
|
|||
|
|
//s.logger.Printf("Onexbet TeamLiveMatchData: sportID %d, channel %s\n", match.SportID, channel)
|
|||
|
|
|
|||
|
|
var (
|
|||
|
|
triggeredStrategyIDs []int64
|
|||
|
|
comments []string
|
|||
|
|
strategyIDs []int
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
for strategyID := range match.Strategies {
|
|||
|
|
strategyIDs = append(strategyIDs, int(strategyID))
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
sort.Ints(strategyIDs)
|
|||
|
|
|
|||
|
|
for _, strategyID := range strategyIDs {
|
|||
|
|
|
|||
|
|
strategy := match.Strategies[int64(strategyID)]
|
|||
|
|
// Если по стратегии уже дали прогноз
|
|||
|
|
//if match.EndedStrategies[strategy.ID()] {
|
|||
|
|
// continue
|
|||
|
|
//}
|
|||
|
|
|
|||
|
|
whyNot, bet, code := strategy.GetTip(match, upd)
|
|||
|
|
|
|||
|
|
switch code {
|
|||
|
|
case Waiting:
|
|||
|
|
// pass
|
|||
|
|
//s.logger.Printf("Waiting: %s\n", whyNot)
|
|||
|
|
|
|||
|
|
// Отправить в websocket
|
|||
|
|
comments = append(comments, fmt.Sprintf("%s:<br>%s", strategy.ShortName(), whyNot))
|
|||
|
|
|
|||
|
|
case Unwatch:
|
|||
|
|
s.logger.Printf("Unwatch: %s\n", whyNot)
|
|||
|
|
|
|||
|
|
triggeredStrategyIDs = append(triggeredStrategyIDs, strategy.ID())
|
|||
|
|
|
|||
|
|
comments = append(comments, fmt.Sprintf("%s:<br>%s", strategy.ShortName(), whyNot))
|
|||
|
|
|
|||
|
|
case Bet:
|
|||
|
|
|
|||
|
|
triggeredStrategyIDs = append(triggeredStrategyIDs, strategy.ID())
|
|||
|
|
|
|||
|
|
var err error
|
|||
|
|
//var tip model.Tip
|
|||
|
|
//var tipPrice float64
|
|||
|
|
|
|||
|
|
tip := model.Tip{
|
|||
|
|
SportID: int64(match.SportID),
|
|||
|
|
StrategyID: strategy.ID(),
|
|||
|
|
MatchID: match.MatchID,
|
|||
|
|
TipTime: time.Now().Unix(),
|
|||
|
|
ChampID: match.Champ.ChampID,
|
|||
|
|
ChampName: match.Champ.Name,
|
|||
|
|
MatchName: fmt.Sprintf("%s - %s", match.Home.CanonicalName, match.Away.CanonicalName),
|
|||
|
|
Market: bet.Market,
|
|||
|
|
Side: bet.Side,
|
|||
|
|
Param: bet.Param,
|
|||
|
|
Price: bet.Price,
|
|||
|
|
CurrentMatchTime: upd.CurrentTime,
|
|||
|
|
OnexbetStatsMatchID: match.OnexbetStatsMatchID,
|
|||
|
|
IsTeamsSwapped: match.IsTeamsSwapped,
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
champRatings, ok := s.champRatings[match.SportID]
|
|||
|
|
if ok {
|
|||
|
|
// обновляем рейтинги чемпионатов
|
|||
|
|
key := ChampRatingKey{
|
|||
|
|
ChampID: tip.ChampID,
|
|||
|
|
StrategyID: tip.StrategyID,
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
rating, ok := champRatings[key]
|
|||
|
|
if ok {
|
|||
|
|
//if (rating.Won+rating.Lost) >= 6 && rating.GetAccuracy() >= 75 {
|
|||
|
|
if rating.GetAccuracy() >= 70 {
|
|||
|
|
tip.IsRated = true
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
s.logger.Printf("Bet: %#v\n", tip)
|
|||
|
|
|
|||
|
|
tip.TipID, err = s.model.AddTip(tip)
|
|||
|
|
if err != nil {
|
|||
|
|
s.logger.Printf("model.AddTip: %s\n", err)
|
|||
|
|
} else {
|
|||
|
|
// Отправить в websocket
|
|||
|
|
// Добавили новый Tip
|
|||
|
|
s.publisher.Publish(channel, WSMessageTipsChanged, nil)
|
|||
|
|
|
|||
|
|
if match.SportID == flashscore.Football {
|
|||
|
|
// Для футбола только рейтинговые чемпионаты
|
|||
|
|
// Отправить в телеграм
|
|||
|
|
if strategy.IsNotifyInTelegram() && tip.IsRated {
|
|||
|
|
// Отправлять ли в телеграм
|
|||
|
|
msg := fmt.Sprintf(strategy.GetTelegramTipPattern(), tip.TipID, strategy.ID(),
|
|||
|
|
match.Zone.Name, match.Champ.Name,
|
|||
|
|
match.Home.CanonicalName, match.Away.CanonicalName,
|
|||
|
|
bet.Price)
|
|||
|
|
|
|||
|
|
telegramChatID, hasChat := sportToTelegramChatID[match.SportID]
|
|||
|
|
if hasChat {
|
|||
|
|
err = sendMessageToTelegramGroup(telegramChatID, msg)
|
|||
|
|
if err != nil {
|
|||
|
|
s.logger.Printf("send tip to Telegram: %s\n", err)
|
|||
|
|
}
|
|||
|
|
} else {
|
|||
|
|
s.logger.Printf("Sport %d hasn't telegram chat\n", match.SportID)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
} else if match.SportID == flashscore.Handball {
|
|||
|
|
if strategy.IsNotifyInTelegram() && tip.IsRated {
|
|||
|
|
param, err := strconv.ParseFloat(bet.Param, 64)
|
|||
|
|
if err != nil {
|
|||
|
|
s.logger.Printf("Wrong bet param: %s; param=%s\n", err, bet.Param)
|
|||
|
|
} else {
|
|||
|
|
|
|||
|
|
var betterParam1, betterParam2 float64
|
|||
|
|
|
|||
|
|
switch strategy.(type) {
|
|||
|
|
case HandballFTOver, HandballP1Over:
|
|||
|
|
betterParam1 = param - 1
|
|||
|
|
betterParam2 = param - 2
|
|||
|
|
|
|||
|
|
case HandballFTUnder, HandballP1Under:
|
|||
|
|
betterParam1 = param + 1
|
|||
|
|
betterParam2 = param + 2
|
|||
|
|
|
|||
|
|
default:
|
|||
|
|
panic(fmt.Sprintf("Unknown strategy %#v\n", strategy))
|
|||
|
|
}
|
|||
|
|
// Для гандбола отправляем все прогнозы
|
|||
|
|
|
|||
|
|
msg := fmt.Sprintf(strategy.GetTelegramTipPattern(), tip.TipID, strategy.ID(),
|
|||
|
|
match.Zone.Name, match.Champ.Name,
|
|||
|
|
match.Home.CanonicalName, match.Away.CanonicalName,
|
|||
|
|
param, bet.Price, param, betterParam1, betterParam2)
|
|||
|
|
|
|||
|
|
telegramChatID, hasChat := sportToTelegramChatID[match.SportID]
|
|||
|
|
if hasChat {
|
|||
|
|
err = sendMessageToTelegramGroup(telegramChatID, msg)
|
|||
|
|
if err != nil {
|
|||
|
|
s.logger.Printf("send tip to Telegram: %s\n", err)
|
|||
|
|
}
|
|||
|
|
} else {
|
|||
|
|
s.logger.Printf("Sport %d hasn't telegram chat\n", match.SportID)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
} else if match.SportID == flashscore.Tennis {
|
|||
|
|
if strategy.IsNotifyInTelegram() {
|
|||
|
|
msg := fmt.Sprintf(bet.TelegramMessage, tip.TipID)
|
|||
|
|
|
|||
|
|
telegramChatID, hasChat := sportToTelegramChatID[match.SportID]
|
|||
|
|
if hasChat {
|
|||
|
|
err = sendMessageToTelegramGroup(telegramChatID, msg)
|
|||
|
|
if err != nil {
|
|||
|
|
s.logger.Printf("send tip to Telegram: %s\n", err)
|
|||
|
|
}
|
|||
|
|
} else {
|
|||
|
|
s.logger.Printf("Sport %d hasn't telegram chat\n", match.SportID)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
for _, strategyID := range triggeredStrategyIDs {
|
|||
|
|
delete(match.Strategies, strategyID)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if len(match.Strategies) == 0 {
|
|||
|
|
// Все стратегии сработали - отписываемся от матча
|
|||
|
|
|
|||
|
|
s.logger.Printf("Unwatch: %d, %s\n", match.SportID, upd.MatchID)
|
|||
|
|
|
|||
|
|
// Матч больше не интересен
|
|||
|
|
delete(s.onexbetMatchToTeamMatch, upd.MatchID)
|
|||
|
|
match.IsInplay = false
|
|||
|
|
|
|||
|
|
s.onexbetAPIClient.Call(api.CallReq{
|
|||
|
|
FuncName: "unwatch",
|
|||
|
|
In: onexbet.WatchReq{
|
|||
|
|
SportID: flashscoreSportToOnexbetSport[match.SportID],
|
|||
|
|
MatchID: upd.MatchID, // onexbetMatchID
|
|||
|
|
},
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if match.SportID == flashscore.Tennis {
|
|||
|
|
if len(comments) > 0 {
|
|||
|
|
s.publisher.Publish(
|
|||
|
|
channel,
|
|||
|
|
WSMessageWhyNot,
|
|||
|
|
WhyNot{
|
|||
|
|
MatchID: match.MatchID,
|
|||
|
|
Comment: strings.Join(comments, "<br><br>"),
|
|||
|
|
Time: "",
|
|||
|
|
HomeGoals: upd.HomeGoals,
|
|||
|
|
AwayGoals: upd.AwayGoals,
|
|||
|
|
GamePace: match.GamePace,
|
|||
|
|
GamePaceFrame: match.GamePaceFrame,
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
} else {
|
|||
|
|
if len(comments) > 0 {
|
|||
|
|
s.publisher.Publish(
|
|||
|
|
channel,
|
|||
|
|
WSMessageWhyNot,
|
|||
|
|
WhyNot{
|
|||
|
|
MatchID: match.MatchID,
|
|||
|
|
Comment: strings.Join(comments, "<br><br>"),
|
|||
|
|
Time: fmt.Sprintf("%s", time.Duration(upd.CurrentTime)*time.Second),
|
|||
|
|
HomeGoals: upd.HomeGoals,
|
|||
|
|
AwayGoals: upd.AwayGoals,
|
|||
|
|
GamePace: match.GamePace,
|
|||
|
|
GamePaceFrame: match.GamePaceFrame,
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/*
|
|||
|
|
var tipTextPattern string
|
|||
|
|
|
|||
|
|
switch strategyID {
|
|||
|
|
case 1:
|
|||
|
|
tipTextPattern = tipTextAlgo1Pattern
|
|||
|
|
|
|||
|
|
case 2:
|
|||
|
|
tipTextPattern = tipTextAlgo2Pattern
|
|||
|
|
|
|||
|
|
case 3:
|
|||
|
|
tipTextPattern = tipTextAlgo3Pattern
|
|||
|
|
|
|||
|
|
case 100:
|
|||
|
|
tipTextPattern = tipTextAlgo100Pattern
|
|||
|
|
}
|
|||
|
|
*/
|
|||
|
|
|
|||
|
|
/*
|
|||
|
|
switch strategyID {
|
|||
|
|
case 1:
|
|||
|
|
var price float64
|
|||
|
|
for _, over := range upd.H1.Total.Over {
|
|||
|
|
if over.Param == "0.5" {
|
|||
|
|
price = over.Price
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
tipPrice = price
|
|||
|
|
|
|||
|
|
tip = model.Tip{
|
|||
|
|
SportID: SportFootball,
|
|||
|
|
StrategyID: int64(strategyID),
|
|||
|
|
MatchID: match.MatchID,
|
|||
|
|
TipTime: time.Now().Unix(),
|
|||
|
|
ChampName: match.Champ.Name,
|
|||
|
|
MatchName: fmt.Sprintf("%s - %s", match.Home.CanonicalName, match.Away.CanonicalName),
|
|||
|
|
Market: MarketTotal1stHalf,
|
|||
|
|
Side: SideOver,
|
|||
|
|
Param: "0.5",
|
|||
|
|
Price: price,
|
|||
|
|
CurrentMatchTime: upd.CurrentTime,
|
|||
|
|
OnexbetStatsMatchID: match.OnexbetStatsMatchID,
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
case 2:
|
|||
|
|
var price float64
|
|||
|
|
for _, over := range upd.Total.Over {
|
|||
|
|
if over.Param == "0.5" {
|
|||
|
|
price = over.Price
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
tipPrice = price
|
|||
|
|
|
|||
|
|
tip = model.Tip{
|
|||
|
|
SportID: SportFootball,
|
|||
|
|
StrategyID: int64(strategyID),
|
|||
|
|
MatchID: match.MatchID,
|
|||
|
|
TipTime: time.Now().Unix(),
|
|||
|
|
ChampName: match.Champ.Name,
|
|||
|
|
MatchName: fmt.Sprintf("%s - %s", match.Home.CanonicalName, match.Away.CanonicalName),
|
|||
|
|
Market: MarketTotal,
|
|||
|
|
Side: SideOver,
|
|||
|
|
Param: "0.5",
|
|||
|
|
Price: price,
|
|||
|
|
CurrentMatchTime: upd.CurrentTime,
|
|||
|
|
OnexbetStatsMatchID: match.OnexbetStatsMatchID,
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
case 3:
|
|||
|
|
var price float64
|
|||
|
|
for _, over := range upd.Total.Over {
|
|||
|
|
if over.Param == "1.5" {
|
|||
|
|
price = over.Price
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
tipPrice = price
|
|||
|
|
|
|||
|
|
tip = model.Tip{
|
|||
|
|
SportID: SportFootball,
|
|||
|
|
StrategyID: int64(strategyID),
|
|||
|
|
MatchID: match.MatchID,
|
|||
|
|
TipTime: time.Now().Unix(),
|
|||
|
|
ChampName: match.Champ.Name,
|
|||
|
|
MatchName: fmt.Sprintf("%s - %s", match.Home.CanonicalName, match.Away.CanonicalName),
|
|||
|
|
Market: MarketTotal,
|
|||
|
|
Side: SideOver,
|
|||
|
|
Param: "1.5",
|
|||
|
|
Price: price,
|
|||
|
|
CurrentMatchTime: upd.CurrentTime,
|
|||
|
|
OnexbetStatsMatchID: match.OnexbetStatsMatchID,
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
case 100:
|
|||
|
|
var price float64
|
|||
|
|
for _, over := range upd.H1.Total.Over {
|
|||
|
|
if over.Param == "0.5" {
|
|||
|
|
price = over.Price
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
tipPrice = price
|
|||
|
|
|
|||
|
|
tip = model.Tip{
|
|||
|
|
SportID: SportFootball,
|
|||
|
|
StrategyID: int64(strategyID),
|
|||
|
|
MatchID: match.MatchID,
|
|||
|
|
TipTime: time.Now().Unix(),
|
|||
|
|
ChampName: match.Champ.Name,
|
|||
|
|
MatchName: fmt.Sprintf("%s - %s", match.Home.CanonicalName, match.Away.CanonicalName),
|
|||
|
|
Market: MarketTotal1stHalf,
|
|||
|
|
Side: SideOver,
|
|||
|
|
Param: "0.5",
|
|||
|
|
Price: price,
|
|||
|
|
CurrentMatchTime: upd.CurrentTime,
|
|||
|
|
OnexbetStatsMatchID: match.OnexbetStatsMatchID,
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
*/
|