diff --git a/config/config.go b/config/config.go index 122f3f6..0e7a0ce 100644 --- a/config/config.go +++ b/config/config.go @@ -1,10 +1,9 @@ package config var ( - BotToken = " " // Define your bot token here - CFBDAPIKey = " " // Define your CFBD API Key here + BotToken = "MTMwMDU3NjEzNTkzMDk3MDI2NA.GuRb2j.1C-wvihlNPCPWz1SXeEmeZcONJNUIirkxgTWW8" + CFBDAPIKey = "ay2L+7JSttYQP9e10qxpAkluht7NHcyEnSqbBUsqWrwVjBcl02QH2XjOaVDnPRBy" ) func LoadConfig() { - // Just leaving this here in case we need to add stuff later. } diff --git a/go.mod b/go.mod index f6d3e19..a243ba5 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module discord-cfb-bot -go 1.19 +go 1.23.0 require github.com/bwmarrin/discordgo v0.28.1 diff --git a/internal/bot/bot.go b/internal/bot/bot.go index ea1e58a..00dc02a 100644 --- a/internal/bot/bot.go +++ b/internal/bot/bot.go @@ -1,41 +1,41 @@ package bot import ( - "discord-cfb-bot/config" - "discord-cfb-bot/internal/clients" - "github.com/bwmarrin/discordgo" - "fmt" - "strings" + "fmt" + "strings" + + "discord-cfb-bot/internal/clients/cfb" // Adjust this import path as needed + "github.com/bwmarrin/discordgo" ) -var Bot *discordgo.Session +// MessageCreate handles incoming messages and triggers the appropriate response +func MessageCreate(s *discordgo.Session, m *discordgo.MessageCreate) { + // Ignore messages from the bot itself + if m.Author.ID == s.State.User.ID { + return + } -func Start() error { - var err error - Bot, err = discordgo.New("Bot " + config.BotToken) + // Command for fetching college football scores + if strings.HasPrefix(m.Content, "!cfb") { + // Extract any arguments (e.g., week number) if needed + weekNumber := 10 // Replace this with dynamic parsing if necessary + + games, err := cfb.FetchScoreboard(weekNumber) if err != nil { - return fmt.Errorf("error creating a Discord session: %w", err) + s.ChannelMessageSend(m.ChannelID, fmt.Sprintf("Error fetching games: %s", err)) + return } - fmt.Println("Discord session created successfully") - - Bot.AddHandler(commandHandler) - err = Bot.Open() - if err != nil { - return fmt.Errorf("Error opening connection: %w", err) + if len(games) == 0 { + s.ChannelMessageSend(m.ChannelID, "No games found for this week.") + return } - fmt.Println("Bot is now running.") - return nil + + // Send formatted game summaries + for _, game := range games { + gameSummary := cfb.FormatGameOutput(game) + s.ChannelMessageSend(m.ChannelID, gameSummary) + } + } } -func commandHandler(s *discordgo.Session, m *discordgo.MessageCreate) { - if m.Author.Bot { - return - } - - if strings.HasPrefix(m.Content, "!s ") { - teamName := strings.TrimSpace(strings.TrimPrefix(m.Content, "!s ")) - response := clients.GetGameInfo(teamName) - s.ChannelMessageSend(m.ChannelID, response) - } -} diff --git a/internal/clients/cfb.go b/internal/clients/cfb.go new file mode 100644 index 0000000..8d1a928 --- /dev/null +++ b/internal/clients/cfb.go @@ -0,0 +1,81 @@ +package cfb + +import ( + "encoding/json" + "errors" + "fmt" + "io/ioutil" + "net/http" + "os" + "time" +) + +// Game represents the structure of a single game +type Game struct { + AwayTeam string `json:"away_team"` + AwayScore string `json:"away_score"` + HomeTeam string `json:"home_team"` + HomeScore string `json:"home_score"` + GameState string `json:"game_state"` + CurrentPeriod string `json:"current_period"` + StartTime string `json:"start_time"` + ContestClock string `json:"contest_clock"` + StartTimeEpoch int64 `json:"start_time_epoch"` +} + +// FetchScoreboard retrieves and parses the scoreboard data from the specified API URL +func FetchScoreboard(weekNumber int) ([]Game, error) { + apiKey := os.Getenv("CFBD_API_KEY") + if apiKey == "" { + return nil, errors.New("missing API key") + } + + url := fmt.Sprintf("https://ncaa.ewnix.net/scoreboard/football/fbs/2024/%d/all-conf", weekNumber) + req, _ := http.NewRequest("GET", url, nil) + req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", apiKey)) + + client := &http.Client{} + resp, err := client.Do(req) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("failed to fetch scoreboard, status code: %d", resp.StatusCode) + } + + body, _ := ioutil.ReadAll(resp.Body) + var data struct { + Games []struct { + Game Game `json:"game"` + } `json:"games"` + } + + if err := json.Unmarshal(body, &data); err != nil { + return nil, fmt.Errorf("error parsing scoreboard: %w", err) + } + + games := make([]Game, len(data.Games)) + for i, g := range data.Games { + // Adjust start time by subtracting 5 hours for upcoming games + if g.Game.GameState == "upcoming" { + epochTime := time.Unix(g.Game.StartTimeEpoch, 0).Add(-5 * time.Hour) + g.Game.StartTime = epochTime.Format("03:04PM ET") + } + games[i] = g.Game + } + + return games, nil +} + +// FormatGameOutput formats the game data into a string +func FormatGameOutput(game Game) string { + if game.GameState == "live" { + return fmt.Sprintf("%s: *%s* %s: *%s* Quarter: %s Time Remaining: %s", + game.AwayTeam, game.AwayScore, game.HomeTeam, game.HomeScore, game.CurrentPeriod, game.ContestClock) + } + return fmt.Sprintf("%s: %s %s: %s Final", + game.AwayTeam, game.AwayScore, game.HomeTeam, game.HomeScore) +} + diff --git a/internal/clients/cfbd_client.go b/internal/clients/cfbd_client.go deleted file mode 100644 index 6ea616f..0000000 --- a/internal/clients/cfbd_client.go +++ /dev/null @@ -1,93 +0,0 @@ -package clients - -import ( - "encoding/json" - "fmt" - "io/ioutil" - "net/http" - "strings" - "time" - - "discord-cfb-bot/config" -) - -type Game struct { - ID int `json:"id"` - StartDate string `json:"startDate"` - Status string `json:"status"` - Period *int `json:"period"` - Clock *string `json:"clock"` - HomeTeam Team `json:"homeTeam"` - AwayTeam Team `json:"awayTeam"` -} - -type Team struct { - Name string `json:"name"` - Points *int `json:"points"` -} - -func GetGameInfo(teamName string) string { - apiURL := "https://apinext.collegefootballdata.com/scoreboard" - req, _ := http.NewRequest("GET", apiURL, nil) - req.Header.Set("Authorization", "Bearer "+config.CFBDAPIKey) // Use the API key from config - - client := &http.Client{} - resp, err := client.Do(req) - if err != nil { - return fmt.Sprintf("Failed to reach the scoreboard API: %v", err) - } - defer resp.Body.Close() - - if resp.StatusCode != http.StatusOK { - return fmt.Sprintf("Failed to reach the scoreboard API. Status code: %d", resp.StatusCode) - } - - body, _ := ioutil.ReadAll(resp.Body) - - var games []Game - if err := json.Unmarshal(body, &games); err != nil { - return "Error parsing the API response." - } - - for _, game := range games { - if strings.Contains(strings.ToLower(game.HomeTeam.Name), strings.ToLower(teamName)) || - strings.Contains(strings.ToLower(game.AwayTeam.Name), strings.ToLower(teamName)) { - - startTime, _ := time.Parse(time.RFC3339, game.StartDate) - adjustedTime := startTime.Add(-5 * time.Hour) // Subtract 5 hours for upcoming games - formattedTime := adjustedTime.Format("03:04 PM") - - if game.Status == "in_progress" { - return fmt.Sprintf("%s: **%d** %s: **%d** | Quarter: %d | Time Remaining: %s", - game.AwayTeam.Name, valueOrZero(game.AwayTeam.Points), - game.HomeTeam.Name, valueOrZero(game.HomeTeam.Points), - *game.Period, derefOrDefault(game.Clock, "00:00")) - } else if game.Status == "scheduled" { - return fmt.Sprintf("Upcoming: %s @ %s at %s Eastern", - game.AwayTeam.Name, game.HomeTeam.Name, formattedTime) - } else if game.Status == "completed" { - return fmt.Sprintf("Final: %s: %d %s: %d", - game.AwayTeam.Name, valueOrZero(game.AwayTeam.Points), - game.HomeTeam.Name, valueOrZero(game.HomeTeam.Points)) - } - } - } - - return "No game found for the specified team." -} - -// Helper function to safely dereference pointers -func derefOrDefault(str *string, defaultVal string) string { - if str != nil { - return *str - } - return defaultVal -} - -// Helper function to return a value or zero if nil -func valueOrZero(val *int) int { - if val != nil { - return *val - } - return 0 -}