This commit is contained in:
2026-07-21 05:29:37 +03:00
commit edb875f197
299 changed files with 140638 additions and 0 deletions

1262
daemon/daemon.go Normal file

File diff suppressed because it is too large Load Diff

77
daemon/daemon_test.go Normal file
View File

@@ -0,0 +1,77 @@
package daemon
import (
"testing"
)
/*
func TestCorrectTotal(t *testing.T) {
//total := 58.5
//total = total - 8
total, _ := strconv.ParseFloat("68.000", 64)
//total := 68.0
total = correctTotal(total, roundUp)
fmt.Printf("%.2f\n", total)
}
*/
/*
func TestOver(t *testing.T) {
s := NewHandballFTOver(StrategyOptions{
ID: 1,
})
match := &TeamMatch{
TipTotalOver: 50.5,
BasicUnderGoalsPerMinute: 1, // 1 гол в минуту
}
upd := onexbet.TeamLiveMatchData{
CurrentTime: 20 * 60, // 30 min
HomeGoals: 12,
AwayGoals: 10,
Total: onexbet.Total{
Over: []onexbet.ParamOffer{
{
Price: 1.41,
Param: "48.5",
},
{
Price: 1.5,
Param: "49.5",
},
{
Price: 1.51,
Param: "50.5",
},
{
Price: 1.65,
Param: "51.5",
},
{
Price: 1.85,
Param: "52.5",
},
},
},
}
comment, bet, code := s.GetTip(match, upd)
fmt.Printf("Code: %d\n", code)
fmt.Printf("Comment: %s\n", comment)
buf, _ := json.MarshalIndent(bet, "", " ")
fmt.Printf("Bet: %s\n", buf)
}
*/
func TestTelegram(t *testing.T) {
err := sendMessageToTelegramGroup("754217958", "Персональное сообщение Максиму")
if err != nil {
t.Fatal(err)
}
}

799
daemon/football.go Normal file
View File

@@ -0,0 +1,799 @@
package daemon
import (
"fmt"
"gordenko.dev/dima/flashscore"
"gordenko.dev/dima/onexbet"
"gordenko.dev/dima/tipper/model"
)
func FootballHistoryProcessor(match *TeamMatch) {
}
/*
Алгоритм 2
ТБ 0.5 в Матче
До матча:
ТБ 2.5 (в матче) <=1.7
В 75% игр был гол
В Лайве:
Сумма атак обычных и опасных >= 135
6 ударов в сторону ворот OFF TARGET
4 удара в створ ON TARGET
До 70 минуты
*/
type FootballOver05 struct {
id int64
notifyInTelegram bool
isChampAccepted func(string) bool
}
func NewFootballOver05(opt StrategyOptions) FootballOver05 {
if opt.ID == 0 {
panic("StrategyID not defined")
}
s := FootballOver05{}
s.id = opt.ID
s.notifyInTelegram = opt.NotifyInTelegram
s.isChampAccepted = opt.IsChampAccepted
return s
}
func (s FootballOver05) ID() int64 {
return s.id
}
func (s FootballOver05) ShortName() string {
return fmt.Sprintf("#%d FT Over 0.5 M", s.id)
}
func (s FootballOver05) IsNotifyInTelegram() bool {
return s.notifyInTelegram
}
func (s FootballOver05) GetTelegramTipPattern() string {
return `Сигнал # %d.
Будет ГОЛ!
Алгоритм %d
Тотал Больше 0.5
Футбол. %s. %s
%s - %s
Коэф. %.3f`
}
func (s FootballOver05) IsNeedToWatch(liveMatch onexbet.LiveMatch) bool {
return true
}
type FootballOver05Notes struct {
PriceOver25 float64 `json:"Over 2.5 price"`
MatchesAnalysed int `json:"matchesAnalysed"`
HasGoalInMatches int `json:"hasGoalInMatches"`
GoalsPercent float64 `json:"goalsPercent"`
ZeroDrawsInH2H int `json:"zeroDrawsInH2H"`
ZeroDrawsPercentInH2H float64 `json:"zeroDrawsPercentInH2H"`
}
func (s FootballOver05) IsInteresting(report *TeamMatch) (notes interface{}, _ bool) {
var (
priceOver25 float64
foundPriceOver25 bool
//f flashscore.TotalOffer
)
for _, total := range report.Odds.FullTimeTotal {
if total.Total != "2.5" {
continue
}
// 2.5
for _, offer := range total.Offers {
if offer.Bookmaker == flashscore.Bookmaker1xBet {
priceOver25 = offer.Over
foundPriceOver25 = true
break
}
}
if foundPriceOver25 {
break
} else {
// 1xBet не нашли
// Берем первый коэф.
if len(total.Offers) > 0 {
priceOver25 = total.Offers[0].Over
foundPriceOver25 = true
}
break
}
}
if priceOver25 > 1.7 {
return
}
var (
matchCount int
hasGoalsInMatchCount int
zeroDrawsInH2HCount int
zeroDrawsPercent float64
)
for _, match := range report.HomeTeamMatches {
if match.HomeGoals > 0 || match.AwayGoals > 0 {
hasGoalsInMatchCount++
}
matchCount++
}
for _, match := range report.AwayTeamMatches {
if match.HomeGoals > 0 || match.AwayGoals > 0 {
hasGoalsInMatchCount++
}
matchCount++
}
for _, match := range report.H2H {
if match.HomeGoals > 0 || match.AwayGoals > 0 {
hasGoalsInMatchCount++
} else {
zeroDrawsInH2HCount++
}
matchCount++
}
if matchCount == 0 {
return
}
// h2hMatchCount - 100%
// zeroDrawsInH2HCount - x%
if zeroDrawsInH2HCount > 0 {
// Не более 2 сухих ничьи
if zeroDrawsInH2HCount > 2 {
return
}
zeroDrawsPercent = float64(len(report.H2H)*100) / float64(zeroDrawsInH2HCount)
// Не более 20% сухих ничьих
if zeroDrawsPercent > 20 {
return
}
}
// matchCount - 100%
// goalsInMatchCount - x%
goalsPercent := float64(hasGoalsInMatchCount*100) / float64(matchCount)
if goalsPercent < 80 {
return
}
obj := FootballOver05Notes{
PriceOver25: priceOver25,
MatchesAnalysed: matchCount,
HasGoalInMatches: hasGoalsInMatchCount,
GoalsPercent: goalsPercent,
ZeroDrawsInH2H: zeroDrawsInH2HCount,
ZeroDrawsPercentInH2H: zeroDrawsPercent,
}
//buf, _ := json.MarshalIndent(obj, "", " ")
return obj, true
}
func (s FootballOver05) GetTip(match *TeamMatch, upd onexbet.TeamLiveMatchData) (_ string, _ *BetDetails, _ TipCode) {
if upd.CurrentTime == 0 {
// ВАЖНО!
// Матч еще не начался. Избегаем деления на 0 при вычислении attacksRatio
return "матч не начался", nil, Waiting
}
// Прогнозы даем до 70 минуты включительно.
maxTime := 70 * 60
if upd.CurrentTime > maxTime {
return "70 минут уже отыграли", nil, Unwatch
}
if upd.HomeGoals > 0 || upd.AwayGoals > 0 {
// Если гол уже забили - выходим
return "гол уже забили", nil, Unwatch
}
// Ищем тотал Больше 0.5
// Минимальный курс
minPrice := 1.5
var (
minPriceCheckPassed bool
currentPrice float64
)
for _, over := range upd.Total.Over {
if over.Param == "0.5" {
if over.Price < minPrice {
return fmt.Sprintf("коэф. %.2f < %.2f", over.Price, minPrice), nil, Waiting
}
currentPrice = over.Price
minPriceCheckPassed = true
break
}
}
if !minPriceCheckPassed {
return "тотал 0.5 не найден", nil, Waiting
}
//
attacks := upd.HomeAttacks + upd.AwayAttacks + upd.HomeDangerousAttacks + upd.AwayDangerousAttacks
shotsOffTarget := upd.HomeShotsOffTarget + upd.AwayShotsOffTarget
shotsOnTarget := upd.HomeShotsOnTarget + upd.AwayShotsOnTarget
if attacks < 135 {
return fmt.Sprintf("%d атак < 135", attacks), nil, Waiting
}
if shotsOffTarget < 6 {
return fmt.Sprintf("%d shotsOffTarget < 6", shotsOffTarget), nil, Waiting
}
if shotsOnTarget < 4 {
return fmt.Sprintf("%d shotsOnTarget < 4", shotsOnTarget), nil, Waiting
}
return "", &BetDetails{
Market: MarketTotal,
Side: SideOver,
Param: "0.5",
Price: currentPrice,
}, Bet
}
func (s FootballOver05) GetResult(tip model.Tip, stats onexbet.TeamMatchResult) (_ *TipResult) {
//buf, _ := json.MarshalIndent(stats, "", " ")
//fmt.Printf("%s\n", buf)
if stats.Status != onexbet.StatusMatchCompleted {
return
}
res := new(TipResult)
goals := stats.HomePoints + stats.AwayPoints
if goals > 0 {
res.Status = model.Won
} else {
res.Status = model.Lost
}
res.Result = fmt.Sprintf("счет: %d-%d", stats.HomePoints, stats.AwayPoints)
return res
}
////////// P1 OVER 0.5
type FootballP1Over05 struct {
id int64
notifyInTelegram bool
isChampAccepted func(string) bool
}
func NewFootballP1Over05(opt StrategyOptions) FootballP1Over05 {
if opt.ID == 0 {
panic("StrategyID not defined")
}
s := FootballP1Over05{}
s.id = opt.ID
s.notifyInTelegram = opt.NotifyInTelegram
s.isChampAccepted = opt.IsChampAccepted
return s
}
func (s FootballP1Over05) ID() int64 {
return s.id
}
func (s FootballP1Over05) ShortName() string {
return fmt.Sprintf("#%d H1 Over 0.5 M", s.id)
}
func (s FootballP1Over05) IsNotifyInTelegram() bool {
return s.notifyInTelegram
}
func (s FootballP1Over05) GetTelegramTipPattern() string {
return `Сигнал # %d.
Будет ГОЛ!
Алгоритм %d
Первый тайм, Тотал Больше 0.5
Футбол. %s. %s
%s - %s
Коэф. %.3f`
}
func (s FootballP1Over05) IsNeedToWatch(liveMatch onexbet.LiveMatch) bool {
if liveMatch.CurrentPeriod == 1 {
return true
}
return false
}
type isInterestingNotesFootballP1Over05 struct {
PriceOver25 float64 `json:"Over 2.5 price"`
MatchesAnalysed int `json:"matchesAnalysed"`
HasGoalInP1Matches int `json:"hasGoalInP1Matches"`
P1GoalsPercent float64 `json:"p1GoalsPercent"`
HasGoalInP1H2HMatches int `json:"hasGoalInP1H2HMatches"`
P1H2HGoalsPercent float64 `json:"p1H2HGoalsPercent"`
}
func (s FootballP1Over05) IsInteresting(report *TeamMatch) (notes interface{}, _ bool) {
var (
priceOver25 float64
foundPriceOver25 bool
)
for _, total := range report.Odds.FullTimeTotal {
if total.Total != "2.5" {
continue
}
// 2.5
for _, offer := range total.Offers {
if offer.Bookmaker == flashscore.Bookmaker1xBet {
priceOver25 = offer.Over
foundPriceOver25 = true
break
}
}
if foundPriceOver25 {
break
} else {
// 1xBet не нашли
// Берем первый коэф.
if len(total.Offers) > 0 {
priceOver25 = total.Offers[0].Over
foundPriceOver25 = true
}
break
}
}
if priceOver25 > 1.58 {
return
}
var (
hasStatsMatchCount int
hasGoalsInP1MatchCount int
hasH2HStatsMatchCount int
hasGoalsInP1H2HMatchCount int
p1H2HGoalsPercent float64
)
for _, match := range report.HomeTeamMatches {
if match.HasScoreByPeriods {
hasStatsMatchCount++
if match.P1HomeGoals > 0 || match.P1AwayGoals > 0 {
hasGoalsInP1MatchCount++
}
}
}
for _, match := range report.AwayTeamMatches {
if match.HasScoreByPeriods {
hasStatsMatchCount++
if match.P1HomeGoals > 0 || match.P1AwayGoals > 0 {
hasGoalsInP1MatchCount++
}
}
}
for _, match := range report.H2H {
if match.HasScoreByPeriods {
hasH2HStatsMatchCount++
if match.P1HomeGoals > 0 || match.P1AwayGoals > 0 {
hasGoalsInP1H2HMatchCount++
}
}
}
if hasStatsMatchCount == 0 {
return
}
// hasStatsMatchCount - 100%
// hasGoalsInP1MatchCount - x%
p1GoalsPercent := float64(hasGoalsInP1MatchCount*100) / float64(hasStatsMatchCount)
if p1GoalsPercent < 75 {
return
}
if hasH2HStatsMatchCount > 0 {
// hasH2HStatsMatchCount - 100%
// hasGoalsInP1H2HMatchCount - x%
p1H2HGoalsPercent = float64(hasGoalsInP1H2HMatchCount*100) / float64(hasH2HStatsMatchCount)
if p1H2HGoalsPercent < 80 {
return
}
}
obj := isInterestingNotesFootballP1Over05{
PriceOver25: priceOver25,
MatchesAnalysed: hasStatsMatchCount,
HasGoalInP1Matches: hasGoalsInP1MatchCount,
P1GoalsPercent: p1GoalsPercent,
HasGoalInP1H2HMatches: hasGoalsInP1H2HMatchCount,
P1H2HGoalsPercent: p1H2HGoalsPercent,
}
//buf, _ := json.MarshalIndent(obj, "", " ")
return obj, true
}
func (s FootballP1Over05) GetTip(match *TeamMatch, upd onexbet.TeamLiveMatchData) (_ string, _ *BetDetails, _ TipCode) {
if upd.CurrentTime == 0 {
// ВАЖНО!
// Матч еще не начался. Избегаем деления на 0 при вычислении attacksRatio
return "матч не начался", nil, Waiting
}
// Прогнозы даем до 20 минуты включительно.
maxTime := 20 * 60
if upd.CurrentTime > maxTime {
return "20 минут уже отыграли", nil, Unwatch
}
if upd.HomeGoals > 0 || upd.AwayGoals > 0 {
// Если гол уже забили - выходим
return "гол уже забили", nil, Unwatch
}
// Ищем тотал Больше 0.5
// Минимальный курс
minPrice := 1.5
var (
minPriceCheckPassed bool
currentPrice float64
)
for _, over := range upd.H1.Total.Over {
if over.Param == "0.5" {
if over.Price < minPrice {
return fmt.Sprintf("коэф. %.2f < %.2f", over.Price, minPrice), nil, Waiting
}
currentPrice = over.Price
minPriceCheckPassed = true
break
}
}
if !minPriceCheckPassed {
return "тотал 0.5 не найден", nil, Waiting
}
// соотношение атак по времени >= 2.1 (за 10 минут от 21 атаки)
attacks := upd.HomeAttacks + upd.AwayAttacks
shotsOffTarget := upd.HomeShotsOffTarget + upd.AwayShotsOffTarget
attacksRatio := float64(attacks*60) / float64(upd.CurrentTime)
if attacksRatio < 2.1 {
return fmt.Sprintf("отношение атак ко времени %.2f < 2.1", attacksRatio), nil, Waiting
}
if shotsOffTarget < 3 {
return fmt.Sprintf("%d shotsOffTarget < 6", shotsOffTarget), nil, Waiting
}
return "", &BetDetails{
Market: MarketTotalH1,
Side: SideOver,
Param: "0.5",
Price: currentPrice,
}, Bet
}
func (s FootballP1Over05) GetResult(tip model.Tip, stats onexbet.TeamMatchResult) (_ *TipResult) {
//buf, _ := json.MarshalIndent(stats, "", " ")
//fmt.Printf("%s\n", buf)
h1, ok := stats.ScoreByPeriods[1]
if !ok {
return
}
_, ok = stats.ScoreByPeriods[2]
if !ok {
return
}
res := new(TipResult)
h1TotalGoals := h1.HomePoints + h1.AwayPoints
if h1TotalGoals > 0 {
res.Status = model.Won
} else {
res.Status = model.Lost
}
res.Result = fmt.Sprintf("1й тайм: %d-%d", h1.HomePoints, h1.AwayPoints)
return res
}
//////////////// OVER 1.5
type FootballOver15 struct {
id int64
notifyInTelegram bool
isChampAccepted func(string) bool
}
func NewFootballOver15(opt StrategyOptions) FootballOver15 {
if opt.ID == 0 {
panic("StrategyID not defined")
}
s := FootballOver15{}
s.id = opt.ID
s.notifyInTelegram = opt.NotifyInTelegram
s.isChampAccepted = opt.IsChampAccepted
return s
}
func (s FootballOver15) ID() int64 {
return s.id
}
func (s FootballOver15) ShortName() string {
return fmt.Sprintf("#%d FT Over 1.5 M", s.id)
}
func (s FootballOver15) IsNotifyInTelegram() bool {
return s.notifyInTelegram
}
func (s FootballOver15) GetTelegramTipPattern() string {
return `Сигнал # %d.
Будет 2й ГОЛ!
Алгоритм %d
Тотал Больше 1.5
Футбол. %s. %s
%s - %s
Коэф. %.3f`
}
func (s FootballOver15) IsNeedToWatch(liveMatch onexbet.LiveMatch) bool {
return true
}
type isInterestingNotesFootballOver15 struct {
PriceOver25 float64 `json:"Over 2.5 price"`
MatchesAnalysed int `json:"matchesAnalysed"`
HasGoalInMatches int `json:"hasGoalInMatches"`
GoalsPercent float64 `json:"goalsPercent"`
FavoritePrice float64 `json:"favoritePrice"`
Favorite string `json:"favorite"`
}
func (s FootballOver15) IsInteresting(report *TeamMatch) (notes interface{}, _ bool) {
var favoritePrice float64 = 2
var favorite string
// Проходим по всем ценам, каждый раз обновляя минимальную
for _, offer := range report.Odds.Winner {
if offer.Home < favoritePrice && offer.Home > 1.01 {
favoritePrice = offer.Home
favorite = "home"
}
if offer.Away < favoritePrice && offer.Away > 1.01 {
favoritePrice = offer.Away
favorite = "away"
}
}
if favoritePrice > 1.4 {
return
}
var (
priceOver25 float64
foundPriceOver25 bool
)
for _, total := range report.Odds.FullTimeTotal {
if total.Total != "2.5" {
continue
}
// 2.5
for _, offer := range total.Offers {
if offer.Bookmaker == flashscore.Bookmaker1xBet {
priceOver25 = offer.Over
foundPriceOver25 = true
break
}
}
if foundPriceOver25 {
break
} else {
// 1xBet не нашли
// Берем первый коэф.
if len(total.Offers) > 0 {
priceOver25 = total.Offers[0].Over
foundPriceOver25 = true
}
break
}
}
if priceOver25 > 1.7 {
return
}
var (
matchCount int
hasGoalsInMatchCount int
)
for _, match := range report.HomeTeamMatches {
if match.HomeGoals > 0 || match.AwayGoals > 0 {
hasGoalsInMatchCount++
}
matchCount++
}
for _, match := range report.AwayTeamMatches {
if match.HomeGoals > 0 || match.AwayGoals > 0 {
hasGoalsInMatchCount++
}
matchCount++
}
for _, match := range report.H2H {
if match.HomeGoals > 0 || match.AwayGoals > 0 {
hasGoalsInMatchCount++
}
matchCount++
}
if matchCount == 0 {
return
}
// matchCount - 100%
// goalsInMatchCount - x%
goalsPercent := float64(hasGoalsInMatchCount*100) / float64(matchCount)
if goalsPercent < 75 {
return
}
obj := isInterestingNotesFootballOver15{
PriceOver25: priceOver25,
MatchesAnalysed: matchCount,
HasGoalInMatches: hasGoalsInMatchCount,
GoalsPercent: goalsPercent,
FavoritePrice: favoritePrice,
Favorite: favorite,
}
report.Favorite = favorite
//buf, _ := json.MarshalIndent(obj, "", " ")
return obj, true
}
func (s FootballOver15) GetTip(match *TeamMatch, upd onexbet.TeamLiveMatchData) (_ string, _ *BetDetails, _ TipCode) {
if upd.CurrentTime == 0 {
// ВАЖНО!
// Матч еще не начался. Избегаем деления на 0 при вычислении attacksRatio
return "матч не начался", nil, Waiting
}
// Прогнозы даем до 70 минуты включительно.
maxTime := 70 * 60
if upd.CurrentTime > maxTime {
return "70 минут уже отыграли", nil, Unwatch
}
goals := upd.HomeGoals + upd.AwayGoals
if goals > 1 {
// Если 2 гола уже забили - выходим
return "забили более 1 гола", nil, Unwatch
}
if goals == 0 {
return "счет 0-0", nil, Waiting
}
if (upd.HomeGoals == 1 && match.Favorite == "away") || (upd.AwayGoals == 1 && match.Favorite == "home") {
// все ок, фаворит уступает - анализируем дальше
} else {
// забил фаворит - далее неинтересно
return "забил фаворит", nil, Unwatch
}
// Ищем тотал Больше 1.5
// Минимальный курс
minPrice := 1.5
var (
minPriceCheckPassed bool
currentPrice float64
)
for _, over := range upd.Total.Over {
if over.Param == "1.5" {
if over.Price < minPrice {
comment := fmt.Sprintf("коэф. %.2f < %.2f", over.Price, minPrice)
return comment, nil, Waiting
}
currentPrice = over.Price
minPriceCheckPassed = true
break
}
}
if !minPriceCheckPassed {
return "тотал 1.5 не найден", nil, Waiting
}
//
attacks := upd.HomeAttacks + upd.AwayAttacks + upd.HomeDangerousAttacks + upd.AwayDangerousAttacks
shotsOffTarget := upd.HomeShotsOffTarget + upd.AwayShotsOffTarget
shotsOnTarget := upd.HomeShotsOnTarget + upd.AwayShotsOnTarget
if attacks < 125 {
return fmt.Sprintf("%d атак < 125", attacks), nil, Waiting
}
if shotsOffTarget < 6 {
return fmt.Sprintf("%d shotsOffTarget < 6", shotsOffTarget), nil, Waiting
}
if shotsOnTarget < 4 {
return fmt.Sprintf("%d shotsOnTarget < 4", shotsOnTarget), nil, Waiting
}
return "", &BetDetails{
Market: MarketTotal,
Side: SideOver,
Param: "1.5",
Price: currentPrice,
}, Bet
}
func (s FootballOver15) GetResult(tip model.Tip, stats onexbet.TeamMatchResult) (_ *TipResult) {
//buf, _ := json.MarshalIndent(stats, "", " ")
//fmt.Printf("%s\n", buf)
if stats.Status != onexbet.StatusMatchCompleted {
return
}
res := new(TipResult)
goals := stats.HomePoints + stats.AwayPoints
if goals > 1 {
res.Status = model.Won
} else {
res.Status = model.Lost
}
res.Result = fmt.Sprintf("счет: %d-%d", stats.HomePoints, stats.AwayPoints)
return res
}

1165
daemon/handball.go Normal file

File diff suppressed because it is too large Load Diff

41
daemon/helpers.go Normal file
View File

@@ -0,0 +1,41 @@
package daemon
import (
"math"
"sort"
"gordenko.dev/dima/onexbet"
)
func isFloatEqual(a, b, eps float64) bool {
if math.Abs(a-b) < eps {
return true
}
return false
}
func SortOffersByParamMaxMin(offers []onexbet.ParamOffer) {
sort.Slice(offers, func(i, j int) bool {
if offers[i].ParamFloat > offers[j].ParamFloat {
return true
}
return false
})
}
func SortOffersByParamMinMax(offers []onexbet.ParamOffer) {
sort.Slice(offers, func(i, j int) bool {
if offers[i].ParamFloat < offers[j].ParamFloat {
return true
}
return false
})
}
func isTennisSetCompleted(score onexbet.PeriodScore) (_ bool) {
if (score.HomePoints == 6 && score.AwayPoints < 5) || score.HomePoints == 7 ||
(score.AwayPoints == 6 && score.HomePoints < 5) || score.AwayPoints == 7 {
return true
}
return false
}

875
daemon/onexbet.go Executable file
View File

@@ -0,0 +1,875 @@
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,
}
}
*/

250
daemon/over05.go Executable file
View File

@@ -0,0 +1,250 @@
package daemon
/*
Футбольная стратегия.
ТБ 0.5 в 1м тайме
*/
import (
"fmt"
"gordenko.dev/dima/flashscore"
"gordenko.dev/dima/onexbet"
"gordenko.dev/dima/tipper/model"
)
/*
Алгоритм 2
ТБ 0.5 в Матче
До матча:
ТБ 2.5 (в матче) <=1.7
В 75% игр был гол
В Лайве:
Сумма атак обычных и опасных >= 135
6 ударов в сторону ворот OFF TARGET
4 удара в створ ON TARGET
До 70 минуты
*/
type Over05 struct {
id int64
notifyInTelegram bool
isChampAccepted func(string) bool
}
func NewOver05(opt StrategyOptions) Over05 {
if opt.ID == 0 {
panic("StrategyID not defined")
}
s := Over05{}
s.id = opt.ID
s.notifyInTelegram = opt.NotifyInTelegram
s.isChampAccepted = opt.IsChampAccepted
return s
}
func (s Over05) ID() int64 {
return s.id
}
func (s Over05) ShortName() string {
return fmt.Sprintf("#%d FT Over 0.5", s.id)
}
func (s Over05) IsNotifyInTelegram() bool {
return s.notifyInTelegram
}
func (s Over05) GetTelegramTipPattern() string {
return `Сигнал # %d.
Будет ГОЛ!
Алгоритм %d
Тотал Больше 0.5
Футбол. %s. %s
%s - %s
Коэф. %.3f`
}
type isInterestingNotesOver05 struct {
PriceOver25 float64 `json:"Over 2.5 price"`
MatchesAnalysed int `json:"matchesAnalysed"`
HasGoalInMatches int `json:"hasGoalInMatches"`
GoalsPercent float64 `json:"goalsPercent"`
}
func (s Over05) IsInteresting(report *TeamMatch) (notes interface{}, _ bool) {
var (
priceOver25 float64
foundPriceOver25 bool
//f flashscore.TotalOffer
)
for _, total := range report.Odds.FullTimeTotal {
if total.Total != "2.5" {
continue
}
// 2.5
for _, offer := range total.Offers {
if offer.Bookmaker == flashscore.Bookmaker1xBet {
priceOver25 = offer.Over
foundPriceOver25 = true
break
}
}
if foundPriceOver25 {
break
} else {
// 1xBet не нашли
// Берем первый коэф.
if len(total.Offers) > 0 {
priceOver25 = total.Offers[0].Over
foundPriceOver25 = true
}
break
}
}
if priceOver25 > 1.7 {
return
}
var (
matchCount int
hasGoalsInMatchCount int
)
for _, match := range report.HomeTeamMatches {
if match.HomeGoals > 0 || match.AwayGoals > 0 {
hasGoalsInMatchCount++
}
matchCount++
}
for _, match := range report.AwayTeamMatches {
if match.HomeGoals > 0 || match.AwayGoals > 0 {
hasGoalsInMatchCount++
}
matchCount++
}
for _, match := range report.H2H {
if match.HomeGoals > 0 || match.AwayGoals > 0 {
hasGoalsInMatchCount++
}
matchCount++
}
if matchCount == 0 {
return
}
// matchCount - 100%
// goalsInMatchCount - x%
goalsPercent := float64(hasGoalsInMatchCount*100) / float64(matchCount)
if goalsPercent < 75 {
return
}
obj := isInterestingNotesOver05{
PriceOver25: priceOver25,
MatchesAnalysed: matchCount,
HasGoalInMatches: hasGoalsInMatchCount,
GoalsPercent: goalsPercent,
}
//buf, _ := json.MarshalIndent(obj, "", " ")
return obj, true
}
func (s Over05) GetTip(match *TeamMatch, upd onexbet.TeamLiveMatchData) (_ string, _ *BetDetails, _ TipCode) {
if upd.CurrentTime == 0 {
// ВАЖНО!
// Матч еще не начался. Избегаем деления на 0 при вычислении attacksRatio
return "матч не начался", nil, Waiting
}
// Прогнозы даем до 70 минуты включительно.
maxTime := 70 * 60
if upd.CurrentTime > maxTime {
return "70 минут уже отыграли", nil, Unwatch
}
if upd.HomeGoals > 0 || upd.AwayGoals > 0 {
// Если гол уже забили - выходим
return "гол уже забит", nil, Unwatch
}
// Ищем тотал Больше 0.5
// Минимальный курс
minPrice := 1.5
var (
minPriceCheckPassed bool
currentPrice float64
)
for _, over := range upd.Total.Over {
if over.Param == "0.5" {
if over.Price < minPrice {
return fmt.Sprintf("коэф. %.2f < %.2f", over.Price, minPrice), nil, Waiting
}
currentPrice = over.Price
minPriceCheckPassed = true
break
}
}
if !minPriceCheckPassed {
return "тотал 0.5 не найден", nil, Waiting
}
//
attacks := upd.HomeAttacks + upd.AwayAttacks + upd.HomeDangerousAttacks + upd.AwayDangerousAttacks
shotsOffTarget := upd.HomeShotsOffTarget + upd.AwayShotsOffTarget
shotsOnTarget := upd.HomeShotsOnTarget + upd.AwayShotsOnTarget
if attacks < 135 {
return fmt.Sprintf("%d атак < 135", attacks), nil, Waiting
}
if shotsOffTarget < 6 {
return fmt.Sprintf("%d shotsOffTarget < 6", shotsOffTarget), nil, Waiting
}
if shotsOnTarget < 4 {
return fmt.Sprintf("%d shotsOnTarget < 4", shotsOnTarget), nil, Waiting
}
return "", &BetDetails{
Market: MarketTotal,
Side: SideOver,
Param: "0.5",
Price: currentPrice,
}, Bet
}
func (s Over05) GetResult(tip model.Tip, stats onexbet.TeamMatchResult) (_ *TipResult) {
//buf, _ := json.MarshalIndent(stats, "", " ")
//fmt.Printf("%s\n", buf)
if stats.Status != onexbet.StatusMatchCompleted {
return
}
res := new(TipResult)
goals := stats.HomePoints + stats.AwayPoints
if goals > 0 {
res.Status = model.Won
} else {
res.Status = model.Lost
}
res.Result = fmt.Sprintf("счет: %d-%d", stats.HomePoints, stats.AwayPoints)
return res
}

275
daemon/over15.go Executable file
View File

@@ -0,0 +1,275 @@
package daemon
/*
Футбольная стратегия.
ТБ 0.5 в 1м тайме
*/
import (
"fmt"
"gordenko.dev/dima/flashscore"
"gordenko.dev/dima/onexbet"
"gordenko.dev/dima/tipper/model"
)
/*
Алгоритм 3
ТБ 1.5 в Матче
До матча:
ТБ 2.5 (в матче) <=1.7
В 75% игр был гол
КФ фаворита <=1.4
В Лайве:
Сумма атак обычных и опасных >= 125
6 ударов в сторону ворот
4 удара в створ
До 70 минуты
*/
type Over15 struct {
id int64
notifyInTelegram bool
isChampAccepted func(string) bool
}
func NewOver15(opt StrategyOptions) Over15 {
if opt.ID == 0 {
panic("StrategyID not defined")
}
s := Over15{}
s.id = opt.ID
s.notifyInTelegram = opt.NotifyInTelegram
s.isChampAccepted = opt.IsChampAccepted
return s
}
func (s Over15) ID() int64 {
return s.id
}
func (s Over15) ShortName() string {
return fmt.Sprintf("#%d FT Over 1.5", s.id)
}
func (s Over15) IsNotifyInTelegram() bool {
return s.notifyInTelegram
}
func (s Over15) GetTelegramTipPattern() string {
return `Сигнал # %d.
Будет 2й ГОЛ!
Алгоритм %d
Тотал Больше 1.5
Футбол. %s. %s
%s - %s
Коэф. %.3f`
}
type isInterestingNotesOver15 struct {
PriceOver25 float64 `json:"Over 2.5 price"`
MatchesAnalysed int `json:"matchesAnalysed"`
HasGoalInMatches int `json:"hasGoalInMatches"`
GoalsPercent float64 `json:"goalsPercent"`
FavoritePrice float64 `json:"favoritePrice"`
}
func (s Over15) IsInteresting(report *TeamMatch) (notes interface{}, _ bool) {
var favoritePrice float64 = 2
for _, offer := range report.Odds.Winner {
if offer.Home < favoritePrice && offer.Home > 1.01 {
favoritePrice = offer.Home
}
if offer.Away < favoritePrice && offer.Away > 1.01 {
favoritePrice = offer.Away
}
}
if favoritePrice > 1.4 {
return
}
var (
priceOver25 float64
foundPriceOver25 bool
)
for _, total := range report.Odds.FullTimeTotal {
if total.Total != "2.5" {
continue
}
// 2.5
for _, offer := range total.Offers {
if offer.Bookmaker == flashscore.Bookmaker1xBet {
priceOver25 = offer.Over
foundPriceOver25 = true
break
}
}
if foundPriceOver25 {
break
} else {
// 1xBet не нашли
// Берем первый коэф.
if len(total.Offers) > 0 {
priceOver25 = total.Offers[0].Over
foundPriceOver25 = true
}
break
}
}
if priceOver25 > 1.7 {
return
}
var (
matchCount int
hasGoalsInMatchCount int
)
for _, match := range report.HomeTeamMatches {
if match.HomeGoals > 0 || match.AwayGoals > 0 {
hasGoalsInMatchCount++
}
matchCount++
}
for _, match := range report.AwayTeamMatches {
if match.HomeGoals > 0 || match.AwayGoals > 0 {
hasGoalsInMatchCount++
}
matchCount++
}
for _, match := range report.H2H {
if match.HomeGoals > 0 || match.AwayGoals > 0 {
hasGoalsInMatchCount++
}
matchCount++
}
if matchCount == 0 {
return
}
// matchCount - 100%
// goalsInMatchCount - x%
goalsPercent := float64(hasGoalsInMatchCount*100) / float64(matchCount)
if goalsPercent < 75 {
return
}
obj := isInterestingNotesOver15{
PriceOver25: priceOver25,
MatchesAnalysed: matchCount,
HasGoalInMatches: hasGoalsInMatchCount,
GoalsPercent: goalsPercent,
FavoritePrice: favoritePrice,
}
//buf, _ := json.MarshalIndent(obj, "", " ")
return obj, true
}
func (s Over15) GetTip(match *TeamMatch, upd onexbet.TeamLiveMatchData) (_ string, _ *BetDetails, _ TipCode) {
if upd.CurrentTime == 0 {
// ВАЖНО!
// Матч еще не начался. Избегаем деления на 0 при вычислении attacksRatio
return "матч не начался", nil, Waiting
}
// Прогнозы даем до 70 минуты включительно.
maxTime := 70 * 60
if upd.CurrentTime > maxTime {
return "70 минут уже отыграли", nil, Unwatch
}
goals := upd.HomeGoals + upd.AwayGoals
if goals > 1 {
// Если 2 гола уже забили - выходим
return "уже забили более 1 гола", nil, Unwatch
}
if goals == 0 {
return "счет 0:0", nil, Waiting
}
// Ищем тотал Больше 1.5
// Минимальный курс
minPrice := 1.5
var (
minPriceCheckPassed bool
currentPrice float64
)
for _, over := range upd.Total.Over {
if over.Param == "1.5" {
if over.Price < minPrice {
return fmt.Sprintf("коэф. %.2f < %.2f", over.Price, minPrice), nil, Waiting
}
currentPrice = over.Price
minPriceCheckPassed = true
break
}
}
if !minPriceCheckPassed {
return "тотал 1.5 не найден", nil, Waiting
}
//
attacks := upd.HomeAttacks + upd.AwayAttacks + upd.HomeDangerousAttacks + upd.AwayDangerousAttacks
shotsOffTarget := upd.HomeShotsOffTarget + upd.AwayShotsOffTarget
shotsOnTarget := upd.HomeShotsOnTarget + upd.AwayShotsOnTarget
if attacks < 125 {
return fmt.Sprintf("%d атак < 125", attacks), nil, Waiting
}
if shotsOffTarget < 6 {
return fmt.Sprintf("%d shotsOffTarget < 6", shotsOffTarget), nil, Waiting
}
if shotsOnTarget < 4 {
return fmt.Sprintf("%d shotsOnTarget < 4", shotsOnTarget), nil, Waiting
}
return "", &BetDetails{
Market: MarketTotal,
Side: SideOver,
Param: "1.5",
Price: currentPrice,
}, Bet
}
func (s Over15) GetResult(tip model.Tip, stats onexbet.TeamMatchResult) (_ *TipResult) {
//buf, _ := json.MarshalIndent(stats, "", " ")
//fmt.Printf("%s\n", buf)
if stats.Status != onexbet.StatusMatchCompleted {
return
}
res := new(TipResult)
goals := stats.HomePoints + stats.AwayPoints
if goals > 1 {
res.Status = model.Won
} else {
res.Status = model.Lost
}
res.Result = fmt.Sprintf("счет: %d-%d", stats.HomePoints, stats.AwayPoints)
return res
}

249
daemon/p1over05.go Executable file
View File

@@ -0,0 +1,249 @@
package daemon
/*
Футбольная стратегия.
ТБ 0.5 в 1м тайме
*/
import (
"fmt"
"gordenko.dev/dima/flashscore"
"gordenko.dev/dima/onexbet"
"gordenko.dev/dima/tipper/model"
)
type P1Over05 struct {
id int64
notifyInTelegram bool
isChampAccepted func(string) bool
}
func NewP1Over05(opt StrategyOptions) P1Over05 {
if opt.ID == 0 {
panic("StrategyID not defined")
}
s := P1Over05{}
s.id = opt.ID
s.notifyInTelegram = opt.NotifyInTelegram
s.isChampAccepted = opt.IsChampAccepted
return s
}
func (s P1Over05) ID() int64 {
return s.id
}
func (s P1Over05) ShortName() string {
return fmt.Sprintf("#%d H1 Over 0.5", s.id)
}
func (s P1Over05) IsNotifyInTelegram() bool {
return s.notifyInTelegram
}
func (s P1Over05) GetTelegramTipPattern() string {
return `Сигнал # %d.
Будет ГОЛ!
Алгоритм %d
Первый тайм, Тотал Больше 0.5
Футбол. %s. %s
%s - %s
Коэф. %.3f`
}
type isInterestingNotesP1Over05 struct {
PriceOver25 float64 `json:"Over 2.5 price"`
MatchesAnalysed int `json:"matchesAnalysed"`
HasGoalInP1Matches int `json:"hasGoalInP1Matches"`
P1GoalsPercent float64 `json:"p1GoalsPercent"`
}
func (s P1Over05) IsInteresting(report *TeamMatch) (notes interface{}, _ bool) {
var (
priceOver25 float64
foundPriceOver25 bool
)
for _, total := range report.Odds.FullTimeTotal {
if total.Total != "2.5" {
continue
}
// 2.5
for _, offer := range total.Offers {
if offer.Bookmaker == flashscore.Bookmaker1xBet {
priceOver25 = offer.Over
foundPriceOver25 = true
break
}
}
if foundPriceOver25 {
break
} else {
// 1xBet не нашли
// Берем первый коэф.
if len(total.Offers) > 0 {
priceOver25 = total.Offers[0].Over
foundPriceOver25 = true
}
break
}
}
if priceOver25 > 1.58 {
return
}
var (
hasStatsMatchCount int
hasGoalsInP1MatchCount int
)
for _, match := range report.HomeTeamMatches {
if match.HasScoreByPeriods {
hasStatsMatchCount++
if match.P1HomeGoals > 0 || match.P1AwayGoals > 0 {
hasGoalsInP1MatchCount++
}
}
}
for _, match := range report.AwayTeamMatches {
if match.HasScoreByPeriods {
hasStatsMatchCount++
if match.P1HomeGoals > 0 || match.P1AwayGoals > 0 {
hasGoalsInP1MatchCount++
}
}
}
for _, match := range report.H2H {
if match.HasScoreByPeriods {
hasStatsMatchCount++
if match.P1HomeGoals > 0 || match.P1AwayGoals > 0 {
hasGoalsInP1MatchCount++
}
}
}
if hasStatsMatchCount == 0 {
return
}
// hasStatsMatchCount - 100%
// hasGoalsInP1MatchCount - x%
p1GoalsPercent := float64(hasGoalsInP1MatchCount*100) / float64(hasStatsMatchCount)
if p1GoalsPercent < 75 {
return
}
obj := isInterestingNotesP1Over05{
PriceOver25: priceOver25,
MatchesAnalysed: hasStatsMatchCount,
HasGoalInP1Matches: hasGoalsInP1MatchCount,
P1GoalsPercent: p1GoalsPercent,
}
//buf, _ := json.MarshalIndent(obj, "", " ")
return obj, true
}
func (s P1Over05) GetTip(match *TeamMatch, upd onexbet.TeamLiveMatchData) (_ string, _ *BetDetails, _ TipCode) {
if upd.CurrentTime == 0 {
// ВАЖНО!
// Матч еще не начался. Избегаем деления на 0 при вычислении attacksRatio
return "матч не начался", nil, Waiting
}
// Прогнозы даем до 20 минуты включительно.
maxTime := 20 * 60
if upd.CurrentTime > maxTime {
return "20 минут уже отыграли", nil, Unwatch
}
if upd.HomeGoals > 0 || upd.AwayGoals > 0 {
// Если гол уже забили - выходим
return "гол уже забили", nil, Unwatch
}
// Ищем тотал Больше 0.5
// Минимальный курс
minPrice := 1.5
var (
minPriceCheckPassed bool
currentPrice float64
)
for _, over := range upd.H1.Total.Over {
if over.Param == "0.5" {
if over.Price < minPrice {
return fmt.Sprintf("коэф. %.2f < %.2f", over.Price, minPrice), nil, Waiting
}
currentPrice = over.Price
minPriceCheckPassed = true
break
}
}
if !minPriceCheckPassed {
return "тотал 0.5 не найден", nil, Waiting
}
// соотношение атак по времени >= 2.1 (за 10 минут от 21 атаки)
attacks := upd.HomeAttacks + upd.AwayAttacks
shotsOffTarget := upd.HomeShotsOffTarget + upd.AwayShotsOffTarget
attacksRatio := float64(attacks*60) / float64(upd.CurrentTime)
if attacksRatio < 2.1 {
return fmt.Sprintf("отношение атак ко времени %.2f < 2.1", attacksRatio), nil, Waiting
}
if shotsOffTarget < 3 {
return fmt.Sprintf("%d shotsOffTarget < 6", shotsOffTarget), nil, Waiting
}
return "", &BetDetails{
Market: MarketTotalH1,
Side: SideOver,
Param: "0.5",
Price: currentPrice,
}, Bet
}
func (s P1Over05) GetResult(tip model.Tip, stats onexbet.TeamMatchResult) (_ *TipResult) {
//buf, _ := json.MarshalIndent(stats, "", " ")
//fmt.Printf("%s\n", buf)
if stats.Status != onexbet.StatusMatchCompleted {
return
}
h1, ok := stats.ScoreByPeriods[1]
if !ok {
return
}
//if stats.H1 == nil || stats.H2 == nil {
//return
//}
res := new(TipResult)
h1Goals := h1.HomePoints + h1.AwayPoints
if h1Goals > 0 {
res.Status = model.Won
} else {
res.Status = model.Lost
}
res.Result = fmt.Sprintf("1й тайм: %d-%d", h1.HomePoints, h1.AwayPoints)
return res
}

165
daemon/pages.go Normal file
View File

@@ -0,0 +1,165 @@
package daemon
import (
"encoding/json"
"fmt"
"time"
"gordenko.dev/dima/flashscore"
"gordenko.dev/dima/timeutil"
"gordenko.dev/dima/tipper/model"
"gordenko.dev/dima/web"
)
func (s *Daemon) PageMatches(state *web.State, reply *web.Reply) (err error) {
buf, _ := json.MarshalIndent(s.teamMatches, "", " ")
//reply.Render("matches", buf)
reply.Write(buf)
return
}
type TipView struct {
Time string
Name string
Champ string
Link string
Result string
Status model.TipStatus
Strategy string
}
type DayTips struct {
Date string
Tips []TipView
}
type PageTipsData struct {
Days []DayTips
}
func (s *Daemon) PageHandballByDays(state *web.State, reply *web.Reply) (err error) {
data, err := s.getPageTipsData(flashscore.Handball)
if err != nil {
return
}
reply.Render("temp_tips", data)
return
}
func (s *Daemon) PageFootballByDays(state *web.State, reply *web.Reply) (err error) {
data, err := s.getPageTipsData(flashscore.Football)
if err != nil {
return
}
reply.Render("temp_tips", data)
return
}
func (s *Daemon) PageTennisByDays(state *web.State, reply *web.Reply) (err error) {
data, err := s.getPageTipsData(flashscore.Tennis)
if err != nil {
return
}
reply.Render("temp_tips", data)
return
}
func (s *Daemon) getPageTipsData(sportID int) (_ PageTipsData, err error) {
tips, err := s.ListSportTips(sportID)
if err != nil {
return
}
var days []DayTips
var currentDay int64
var dayTips DayTips
for _, tip := range tips {
if tip.Status != model.Won && tip.Status != model.Lost {
continue
}
tm := time.Unix(tip.TipTime, 0)
tm = timeutil.FirstSecondInPeriod(tm, "d")
//if err != nil {
// return
//}
day := tm.Unix()
if day != currentDay {
if currentDay != 0 {
days = append(days, dayTips)
}
dayTips = DayTips{
Date: tm.Format("Monday Jan 2, 2006"),
}
currentDay = day
}
dayTips.Tips = append(dayTips.Tips, TipView{
Time: tm.Format("15:04"),
Name: tip.MatchName,
Champ: tip.ChampName,
Link: fmt.Sprintf("https://www.flashscore.com/match/%s/#h2h;overall", tip.MatchID),
Result: tip.Result,
Status: tip.Status,
Strategy: tip.Strategy,
})
}
if currentDay != 0 {
days = append(days, dayTips)
}
return PageTipsData{
Days: days,
}, nil
}
/*
func (s *Tipper) pageLinked(state *web.State, reply *web.Reply) (err error) {
//reply.Render("aliases", nil)
var list []*interestingFootballMatch
for _, x := range s.onexbetMatchToInterestingFootballMatch {
list = append(list, x)
}
buf, _ := json.MarshalIndent(list, "", " ")
reply.WriteString(string(buf))
return
}
func (s *Tipper) pageWatch(state *web.State, reply *web.Reply) (err error) {
//reply.Render("aliases", nil)
var matchID string
state.Val("id", &matchID)
s.onexbetAPIClient.Call("watch", matchID)
reply.WriteString(matchID)
return
}
func (s *Tipper) pageUnwatch(state *web.State, reply *web.Reply) (err error) {
//reply.Render("aliases", nil)
var matchID string
state.Val("id", &matchID)
s.onexbetAPIClient.Call("unwatch", matchID)
reply.WriteString(matchID)
return
}
*/

55
daemon/telegram.go Executable file
View File

@@ -0,0 +1,55 @@
package daemon
import (
"fmt"
"net/http"
"net/url"
"time"
"gordenko.dev/dima/httpreq"
)
// @getidsbot - показывает ID чата
const (
// tipper, sport_oracle_bot
//telegramBotToken = "1059580991:AAFHO4RrQCIF-JWYQI2em1D5aLJ4KbjDPZU"
telegramBotToken = "5637682609:AAH2MJG-r0k9EIUQMagdZOXFUjY4nid514I"
//telegramChannel = "@Bet.One.Group"
tennisTelegramChatID = "-1001189544230"
footballTelegramChatID = "-1001480163278"
handballTelegramChatID = "-1001372665077"
)
var (
telegramSendMessageURL = fmt.Sprintf("https://api.telegram.org/bot%s/sendMessage", telegramBotToken)
)
//chat_id=[MY_CHANNEL_NAME]&text=[MY_MESSAGE_TEXT]
func sendMessageToTelegramGroup(telegramChatID string, msg string) (err error) {
data := make(url.Values)
data.Set("chat_id", telegramChatID)
data.Set("text", msg)
data.Set("parse_mode", "HTML")
u := telegramSendMessageURL + "?" + data.Encode()
req, err := http.NewRequest("GET", u, nil)
if err != nil {
return
}
resp, err := httpreq.Send(req, 10*time.Second)
if err != nil {
return
}
if resp.StatusCode != 200 {
err = fmt.Errorf("StatusCode: %d, Body: %s", resp.StatusCode, resp.Body)
return
}
return
}

1125
daemon/tennis.go Normal file

File diff suppressed because it is too large Load Diff