commit 5d03c6aa090059ef3f0e4f4bfc4bef31d081ef9e Author: phlux Date: Mon Oct 28 20:33:40 2024 -0400 First commit diff --git a/README.md b/README.md new file mode 100644 index 0000000..7018391 --- /dev/null +++ b/README.md @@ -0,0 +1,105 @@ +# Discord College Football Bot + +A Discord bot written in Go that provides up-to-date college football scores and schedules. It responds to commands to show recent or upcoming games for specific teams using the [College Football Data API](https://collegefootballdata.com/). + +## Features + +- Retrieves college football scores by team, including live scores (for Patreon members) +- Provides upcoming game schedules if a game hasn’t started +- Designed to run on a Discord server, responds to commands in real-time + +## Commands + +- **!s ``** — Retrieves the score for the most recent game or the upcoming game schedule if the game hasn’t started. + +Example: +``` +!s Alabama +``` + +## Setup + +### Prerequisites + +- **Go** (1.19 or later) +- **College Football Data API key**: Sign up at [College Football Data](https://collegefootballdata.com/) and obtain an API key. +- **Discord Bot Token**: Set up a bot on the [Discord Developer Portal](https://discord.com/developers/applications) to obtain a token. + +### Installation + +1. **Clone the repository**: + ```bash + git clone https://github.com/yourusername/discord-cfb-bot.git + cd discord-cfb-bot + ``` + +2. **Configure API keys**: + - In `config/config.go`, set the `CFBDAPIKey` with your College Football Data API key and your Discord bot token. + +3. **Run the bot**: + - To test locally: + ```bash + go run main.go + ``` + - To run in the background: + - Use `nohup`, `screen`, `tmux`, or create a `systemd` service (see below for options). + +### Running the Bot in the Background + +You can keep the bot running in the background by using tools like `nohup`, `screen`, or `tmux`. For production servers, consider creating a `systemd` service. + +Example with `nohup`: +```bash +nohup go run main.go > bot.log 2>&1 & +``` + +### Deployment with `systemd` (Linux) + +1. Create a `systemd` service file at `/etc/systemd/system/discord-cfb-bot.service`: + ```ini + [Unit] + Description=Discord College Football Bot + After=network.target + + [Service] + ExecStart=/usr/local/go/bin/go run /path/to/main.go + WorkingDirectory=/path/to/discord-cfb-bot + StandardOutput=append:/path/to/bot.log + StandardError=append:/path/to/bot.log + Restart=always + User=yourusername + + [Install] + WantedBy=multi-user.target + ``` + +2. **Enable and start the service**: + ```bash + sudo systemctl enable discord-cfb-bot + sudo systemctl start discord-cfb-bot + ``` + +## Usage + +Invite the bot to your server and type commands in any text channel where the bot has permissions. For example: +``` +!s Texas A&M +``` + +The bot will reply with the score or the upcoming game schedule for the team. + +## Project Structure + +- `main.go`: Entry point of the bot +- `config/config.go`: Configuration file for the API keys and other settings +- `clients/cfbd_client.go`: Client for interacting with the College Football Data API +- `bot/bot.go`: Discord bot setup and command handling + +## Contributions + +Contributions are welcome! Feel free to submit a pull request to improve the bot or add new features. + +## License + +This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for details. + diff --git a/bot/bot.go b/bot/bot.go new file mode 100644 index 0000000..6701332 --- /dev/null +++ b/bot/bot.go @@ -0,0 +1,41 @@ +package bot + +import ( + "discord-cfb-bot/config" + "discord-cfb-bot/clients" + "github.com/bwmarrin/discordgo" + "fmt" + "strings" +) + +var Bot *discordgo.Session + +func Start() error { + var err error + Bot, err = discordgo.New("Bot " + config.BotToken) + if err != nil { + return fmt.Errorf("error creating a Discord session: %w", err) + } + + fmt.Println("Discord session created successfully") + + Bot.AddHandler(commandHandler) + err = Bot.Open() + if err != nil { + return fmt.Errorf("Error opening connection: %w", err) + } + fmt.Println("Bot is now running.") + return nil +} + +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/bot/handlers.go b/bot/handlers.go new file mode 100644 index 0000000..948bc0b --- /dev/null +++ b/bot/handlers.go @@ -0,0 +1,3 @@ +package bot + +// I'll use this eventually diff --git a/clients/cfbd_client.go b/clients/cfbd_client.go new file mode 100644 index 0000000..58c79b7 --- /dev/null +++ b/clients/cfbd_client.go @@ -0,0 +1,62 @@ +package clients + +import ( + "discord-cfb-bot/config" + "encoding/json" + "fmt" + "net/http" + "net/url" + "time" +) + +const seasonStartDate = "2024-08-28" + +type Game struct { + HomeTeam string `json:"home_team"` + AwayTeam string `json:"away_team"` + HomePoints *int `json:"home_points"` + AwayPoints *int `json:"away_points"` + StartDateTime string `json:"start_date"` +} + +func getCurrentWeek() int { + startDate, _ := time.Parse("2006-01-02", seasonStartDate) + weeks := int(time.Since(startDate).Hours() / (24 * 7)) + return weeks + 1 // +1 to adjust to the 1-based week number +} + +func GetGameInfo(teamName string) string { + currentWeek := getCurrentWeek() + + // Use url.QueryEscape to properly encode team names with special characters + encodedTeamName := url.QueryEscape(teamName) + url := fmt.Sprintf("https://api.collegefootballdata.com/games?year=%d&week=%d&team=%s", time.Now().Year(), currentWeek, encodedTeamName) + + req, _ := http.NewRequest("GET", url, nil) + req.Header.Set("Authorization", "Bearer "+config.CFBDAPIKey) + + client := &http.Client{} + resp, err := client.Do(req) + if err != nil { + fmt.Println("Error fetching data:", err) + return "Error fetching game info." + } + defer resp.Body.Close() + + var games []Game + if err := json.NewDecoder(resp.Body).Decode(&games); err != nil { + fmt.Println("Error decoding response:", err) + return "Error parsing game data." + } + + for _, game := range games { + startTime, _ := time.Parse(time.RFC3339, game.StartDateTime) + if time.Now().After(startTime) && game.HomePoints != nil && game.AwayPoints != nil { + return fmt.Sprintf("%s: %d %s: %d", game.AwayTeam, *game.AwayPoints, game.HomeTeam, *game.HomePoints) + } else if time.Now().Before(startTime) { + return fmt.Sprintf("%s @ %s %s Eastern", game.AwayTeam, game.HomeTeam, startTime.Format("03:04 PM")) + } + } + + return "No recent or upcoming game data found." +} diff --git a/config/config.go b/config/config.go new file mode 100644 index 0000000..122f3f6 --- /dev/null +++ b/config/config.go @@ -0,0 +1,10 @@ +package config + +var ( + BotToken = " " // Define your bot token here + CFBDAPIKey = " " // Define your CFBD API Key here +) + +func LoadConfig() { + // Just leaving this here in case we need to add stuff later. +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..f6d3e19 --- /dev/null +++ b/go.mod @@ -0,0 +1,11 @@ +module discord-cfb-bot + +go 1.19 + +require github.com/bwmarrin/discordgo v0.28.1 + +require ( + github.com/gorilla/websocket v1.4.2 // indirect + golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b // indirect + golang.org/x/sys v0.0.0-20201119102817-f84b799fce68 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..e5a04a3 --- /dev/null +++ b/go.sum @@ -0,0 +1,12 @@ +github.com/bwmarrin/discordgo v0.28.1 h1:gXsuo2GBO7NbR6uqmrrBDplPUx2T3nzu775q/Rd1aG4= +github.com/bwmarrin/discordgo v0.28.1/go.mod h1:NJZpH+1AfhIcyQsPeuBKsUtYrRnjkyu0kIVMCHkZtRY= +github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= +github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b h1:7mWr3k41Qtv8XlltBkDkl8LoP3mpSgBW8BUoxtEdbXg= +golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68 h1:nxC68pudNYkKU6jWhgrqdreuFiOQWj1Fs7T3VrH4Pjw= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= diff --git a/main.go b/main.go new file mode 100644 index 0000000..b7c36f5 --- /dev/null +++ b/main.go @@ -0,0 +1,18 @@ +package main + +import ( + "discord-cfb-bot/bot" + "discord-cfb-bot/config" + "fmt" +) + +func main() { + config.LoadConfig() + err := bot.Start() + if err != nil { + fmt.Println("Error starting the bot:", err) + return + } + + select {} +} diff --git a/utils/time.go b/utils/time.go new file mode 100644 index 0000000..7c8833b --- /dev/null +++ b/utils/time.go @@ -0,0 +1,9 @@ +package utils + +import ( + "time" +) + +func ParseGameTime(gameTime string) (time.Time, error) { + return time.Parse("03:04 PM", gameTime) +}