158 lines
4.3 KiB
Go
158 lines
4.3 KiB
Go
package odds
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"strconv"
|
|
"time"
|
|
|
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/repository"
|
|
)
|
|
|
|
type ServiceImpl struct {
|
|
token string
|
|
store *repository.Store
|
|
}
|
|
|
|
func New(token string, store *repository.Store) *ServiceImpl {
|
|
return &ServiceImpl{token: token, store: store}
|
|
}
|
|
|
|
func (s *ServiceImpl) FetchNonLiveOdds(ctx context.Context) error {
|
|
sportIDs := []int{1, 13, 78, 18, 91, 16, 17}
|
|
|
|
for _, sportID := range sportIDs {
|
|
upcomingURL := fmt.Sprintf("https://api.b365api.com/v1/bet365/upcoming?sport_id=%d&token=%s", sportID, s.token)
|
|
resp, err := http.Get(upcomingURL)
|
|
if err != nil {
|
|
fmt.Printf("❌ Failed to fetch upcoming for sport_id=%d: %v\n", sportID, err)
|
|
continue
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
body, _ := io.ReadAll(resp.Body)
|
|
var data struct {
|
|
Success int `json:"success"`
|
|
Results []map[string]interface{} `json:"results"`
|
|
}
|
|
if err := json.Unmarshal(body, &data); err != nil || data.Success != 1 {
|
|
fmt.Printf("❌ Failed to decode upcoming for sport_id=%d\nRaw: %s\n", sportID, string(body))
|
|
continue
|
|
}
|
|
|
|
for _, ev := range data.Results {
|
|
if getString(ev["type"]) != "EV" {
|
|
continue
|
|
}
|
|
eventID := getString(ev["ID"])
|
|
if eventID == "" {
|
|
continue
|
|
}
|
|
|
|
prematchURL := fmt.Sprintf("https://api.b365api.com/v3/bet365/prematch?token=%s&FI=%s", s.token, eventID)
|
|
oddsResp, err := http.Get(prematchURL)
|
|
if err != nil {
|
|
fmt.Printf("❌ Odds fetch failed for event_id=%s: %v\n", eventID, err)
|
|
continue
|
|
}
|
|
defer oddsResp.Body.Close()
|
|
|
|
oddsBody, _ := io.ReadAll(oddsResp.Body)
|
|
var oddsData struct {
|
|
Success int `json:"success"`
|
|
Results []struct {
|
|
EventID string `json:"event_id"`
|
|
FI string `json:"FI"`
|
|
AsianLines OddsSection `json:"asian_lines"`
|
|
Goals OddsSection `json:"goals"`
|
|
} `json:"results"`
|
|
}
|
|
if err := json.Unmarshal(oddsBody, &oddsData); err != nil || oddsData.Success != 1 || len(oddsData.Results) == 0 {
|
|
fmt.Printf("❌ Failed odds decode for event_id=%s\nRaw: %s\n", eventID, string(oddsBody))
|
|
continue
|
|
}
|
|
|
|
result := oddsData.Results[0]
|
|
finalID := result.EventID
|
|
if finalID == "" {
|
|
finalID = result.FI
|
|
}
|
|
if finalID == "" {
|
|
fmt.Println("⚠️ Skipping event with missing final ID.")
|
|
continue
|
|
}
|
|
|
|
saveOdds := func(sectionName string, section OddsSection) error {
|
|
if len(section.Sp) == 0 {
|
|
fmt.Printf("⚠️ No odds in section '%s' for event_id=%s\n", sectionName, finalID)
|
|
return nil
|
|
}
|
|
|
|
updatedAtUnix, _ := strconv.ParseInt(section.UpdatedAt, 10, 64)
|
|
updatedAt := time.Unix(updatedAtUnix, 0)
|
|
|
|
for marketType, market := range section.Sp {
|
|
if len(market.Odds) == 0 {
|
|
fmt.Printf("⚠️ No odds for marketType=%s in section=%s\n", marketType, sectionName)
|
|
continue
|
|
}
|
|
|
|
marketRecord := domain.Market{
|
|
EventID: finalID,
|
|
FI: result.FI,
|
|
MarketCategory: sectionName,
|
|
MarketType: marketType,
|
|
MarketName: market.Name,
|
|
MarketID: market.ID,
|
|
UpdatedAt: updatedAt,
|
|
Odds: market.Odds,
|
|
}
|
|
|
|
s.store.SaveNonLiveOdd(ctx, marketRecord)
|
|
fmt.Printf("✅ STORED MARKET: event_id=%s | type=%s | name=%s\n", finalID, marketType, market.Name)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
if err := saveOdds("asian_lines", result.AsianLines); err != nil {
|
|
fmt.Printf("⚠️ Skipping event %s due to asian_lines error: %v\n", finalID, err)
|
|
continue
|
|
}
|
|
if err := saveOdds("goals", result.Goals); err != nil {
|
|
fmt.Printf("⚠️ Skipping event %s due to goals error: %v\n", finalID, err)
|
|
continue
|
|
}
|
|
|
|
fmt.Printf("✅ Done storing all odds for event_id=%s\n", finalID)
|
|
}
|
|
}
|
|
|
|
fmt.Println("✅ All non-live odds fetched and stored.")
|
|
return nil
|
|
}
|
|
|
|
// Odds structures
|
|
|
|
type OddsMarket struct {
|
|
ID string `json:"id"`
|
|
Name string `json:"name"`
|
|
Odds []json.RawMessage `json:"odds"`
|
|
}
|
|
|
|
type OddsSection struct {
|
|
UpdatedAt string `json:"updated_at"`
|
|
Sp map[string]OddsMarket `json:"sp"`
|
|
}
|
|
|
|
// Helper
|
|
func getString(v interface{}) string {
|
|
if str, ok := v.(string); ok {
|
|
return str
|
|
}
|
|
return ""
|
|
}
|