Compare commits
10 Commits
cd33fd2584
...
main
Author | SHA1 | Date | |
---|---|---|---|
d8bbab483f | |||
2524404d33 | |||
36f03c2281 | |||
385bd4c7bf | |||
c1a20533da | |||
551f077158 | |||
fa7185ec01 | |||
8876f134b1 | |||
5503df6270 | |||
56d4158d8e |
28
Dockerfile
Normal file
28
Dockerfile
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
# Use the official Go 1.23 image for building the application
|
||||||
|
FROM golang:1.23-alpine AS builder
|
||||||
|
|
||||||
|
# Set the working directory inside the container
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Copy the Go modules manifest and download dependencies
|
||||||
|
COPY go.mod go.sum ./
|
||||||
|
RUN go mod download
|
||||||
|
|
||||||
|
# Copy the rest of the application source code
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
# Build the Go application from the appropriate directory inside 'cmd'
|
||||||
|
RUN go build -o discord-scores-bot ./cmd/main.go
|
||||||
|
|
||||||
|
# Create a minimal runtime image
|
||||||
|
FROM alpine:latest
|
||||||
|
RUN apk --no-cache add ca-certificates
|
||||||
|
|
||||||
|
# Set the working directory
|
||||||
|
WORKDIR /root/
|
||||||
|
|
||||||
|
# Copy the built Go binaries from the builder stage
|
||||||
|
COPY --from=builder /app/discord-scores-bot .
|
||||||
|
|
||||||
|
# Run the bot (use separate Dockerfiles if needed for distinct deployments)
|
||||||
|
CMD ["./discord-scores-bot"]
|
118
README.md
118
README.md
@@ -1,105 +1,71 @@
|
|||||||
# 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/).
|
# Discord Scores Bot
|
||||||
|
|
||||||
|
**Discord Scores Bot** is a powerful, user-friendly bot designed to bring real-time college sports scores and updates directly to your Discord server. Whether you follow college football or basketball, this bot keeps you up-to-date with live game information, final scores, and upcoming match schedules.
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
- Retrieves college football scores by team, including live scores (for Patreon members)
|
- **Live Score Updates**: Get real-time scores for ongoing games.
|
||||||
- Provides upcoming game schedules if a game hasn’t started
|
- **Upcoming Matches**: Check when your favorite teams are playing next.
|
||||||
- Designed to run on a Discord server, responds to commands in real-time
|
- **Final Scores**: View results from completed games.
|
||||||
|
- **Team Search**: Search for scores using team names.
|
||||||
|
- **Date & Week Specification**: Specify dates (e.g., `MM/DD`) for basketball and weeks for football to view related games.
|
||||||
|
|
||||||
## Commands
|
## Supported Commands
|
||||||
|
|
||||||
- **!s `<team_name>`** — Retrieves the score for the most recent game or the upcoming game schedule if the game hasn’t started.
|
- `!cfb [team] [week]`: Retrieves college football game details for the specified team and week (current week if not specified).
|
||||||
|
- `!cbb [team] [MM/DD]`: Retrieves college basketball game details for the specified team and date (today's date if not specified).
|
||||||
|
|
||||||
Example:
|
## Installation
|
||||||
```
|
|
||||||
!s Alabama
|
|
||||||
```
|
|
||||||
|
|
||||||
## Setup
|
1. Clone this repository:
|
||||||
|
|
||||||
### 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
|
```bash
|
||||||
git clone https://github.com/yourusername/discord-cfb-bot.git
|
git clone https://your-repo-url/discord-scores-bot.git
|
||||||
cd discord-cfb-bot
|
cd discord-scores-bot
|
||||||
```
|
```
|
||||||
|
|
||||||
2. **Configure API keys**:
|
2. Install dependencies:
|
||||||
- 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
|
```bash
|
||||||
go run main.go
|
go mod tidy
|
||||||
```
|
|
||||||
- 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**:
|
3. Create a `.env` file and add your Discord bot token:
|
||||||
|
```env
|
||||||
|
BOT_TOKEN=your-discord-bot-token
|
||||||
|
```
|
||||||
|
|
||||||
|
4. Run the bot:
|
||||||
```bash
|
```bash
|
||||||
sudo systemctl enable discord-cfb-bot
|
go run cmd/main.go
|
||||||
sudo systemctl start discord-cfb-bot
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
Invite the bot to your server and type commands in any text channel where the bot has permissions. For example:
|
- Invite the bot to your Discord server and type `!cfb [team] [week]` or `!cbb [team] [MM/DD]` to get game information.
|
||||||
```
|
- If a date or week is not specified, the bot defaults to today's date or the current week.
|
||||||
!s Texas A&M
|
|
||||||
```
|
|
||||||
|
|
||||||
The bot will reply with the score or the upcoming game schedule for the team.
|
## Examples
|
||||||
|
|
||||||
## Project Structure
|
- `!cfb Alabama`: Get details of Alabama's football games for the current week.
|
||||||
|
- `!cbb Duke 11/10`: Get details of Duke's basketball game on November 10th.
|
||||||
|
|
||||||
- `main.go`: Entry point of the bot
|
## Configuration
|
||||||
- `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
|
The bot is configured to use pre-defined API endpoints for fetching scores:
|
||||||
|
- College football: `https://ncaa.ewnix.net/football/fbs`
|
||||||
|
- College basketball: `https://ncaa.ewnix.net/scoreboard/basketball-men/d1`
|
||||||
|
|
||||||
Contributions are welcome! Feel free to submit a pull request to improve the bot or add new features.
|
Ensure that your bot has the necessary permissions to read and send messages in the channels where it's active.
|
||||||
|
|
||||||
|
## Contribution
|
||||||
|
|
||||||
|
Contributions are welcome! Feel free to open an issue or submit a pull request to improve the bot or add new features.
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for details.
|
This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for details.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Stay connected with your favorite college sports and enhance your Discord experience with **Discord Scores Bot**!
|
||||||
|
|
||||||
|
@@ -1,8 +1,8 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"discord-cfb-bot/internal/bot"
|
"discord-scores-bot/internal/bot"
|
||||||
"discord-cfb-bot/config"
|
"discord-scores-bot/config"
|
||||||
"fmt"
|
"fmt"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@@ -1,12 +1,11 @@
|
|||||||
package config
|
package config
|
||||||
|
|
||||||
var BotToken = " "
|
var BotToken = ""
|
||||||
|
|
||||||
var CustomTeamNames = map[string]string{
|
var CustomTeamNames = map[string]string{
|
||||||
"UGA": "Georgia",
|
"bama": "Alabama",
|
||||||
"Booger Eaters": "Auburn",
|
"uga": "Georgia",
|
||||||
"Rape Enablers": "LSU",
|
"buttchuggers": "Tennessee",
|
||||||
"Checkerboard Clowns": "Tennessee",
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func LoadConfig() {
|
func LoadConfig() {
|
||||||
|
@@ -1,9 +1,9 @@
|
|||||||
package bot
|
package bot
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"discord-cfb-bot/config"
|
"discord-scores-bot/config"
|
||||||
cfbClient "discord-cfb-bot/internal/clients/cfb"
|
cfbClient "discord-scores-bot/internal/clients/cfb"
|
||||||
cbbClient "discord-cfb-bot/internal/clients/cbb"
|
cbbClient "discord-scores-bot/internal/clients/cbb"
|
||||||
"github.com/bwmarrin/discordgo"
|
"github.com/bwmarrin/discordgo"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
@@ -35,12 +35,20 @@ type Team struct {
|
|||||||
func GetGameInfo(teamName, date string) string {
|
func GetGameInfo(teamName, date string) string {
|
||||||
// If no date is provided, use today's date
|
// If no date is provided, use today's date
|
||||||
if date == "" {
|
if date == "" {
|
||||||
date = time.Now().Format("2006/01/02") // Format as YYYY/MM/DD
|
// Load Central Time location
|
||||||
|
loc, err := time.LoadLocation("America/Chicago")
|
||||||
|
if err != nil {
|
||||||
|
loc = time.FixedZone("CST", -6*60*60) // Fallback to a fixed offset if loading fails
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get current time in Central Time
|
||||||
|
currentTime := time.Now().In(loc)
|
||||||
|
date = currentTime.Format("2006/01/02") // Format as YYYY/MM/DD in Central Time
|
||||||
} else {
|
} else {
|
||||||
// Check if the last 5 characters are in MM/DD format for date input
|
// Check if the last 5 characters are in MM/DD format for date input
|
||||||
if len(date) == 5 && unicode.IsDigit(rune(date[0])) && unicode.IsDigit(rune(date[1])) && date[2] == '/' &&
|
if len(date) == 5 && unicode.IsDigit(rune(date[0])) && unicode.IsDigit(rune(date[1])) && date[2] == '/' &&
|
||||||
unicode.IsDigit(rune(date[3])) && unicode.IsDigit(rune(date[4])) {
|
unicode.IsDigit(rune(date[3])) && unicode.IsDigit(rune(date[4])) {
|
||||||
date = fmt.Sprintf("2024/%s", strings.ReplaceAll(date, "/", "/")) // Add the year and reformat
|
date = fmt.Sprintf("2024/%s", date) // Add the year and format
|
||||||
} else {
|
} else {
|
||||||
return "Invalid date format. Please use MM/DD."
|
return "Invalid date format. Please use MM/DD."
|
||||||
}
|
}
|
||||||
@@ -89,8 +97,16 @@ func fetchAndParseGames(apiURL, teamName string) string {
|
|||||||
game.Game.Home.Names.Short, game.Game.Home.Score,
|
game.Game.Home.Names.Short, game.Game.Home.Score,
|
||||||
currentPeriod, game.Game.ContestClock)
|
currentPeriod, game.Game.ContestClock)
|
||||||
} else if game.Game.GameState == "pre" {
|
} else if game.Game.GameState == "pre" {
|
||||||
gameInfo = fmt.Sprintf("Upcoming: %s @ %s on %s at %s ET",
|
startTime, err := time.Parse("03:04PM ET", game.Game.StartTime)
|
||||||
|
if err == nil {
|
||||||
|
startTime = startTime.Add(-1 * time.Hour) // Subtract 1 hour for Central Time
|
||||||
|
formattedTime := startTime.Format("03:04 PM CT")
|
||||||
|
gameInfo = fmt.Sprintf("Upcoming: %s @ %s on %s at %s",
|
||||||
|
game.Game.Away.Names.Short, game.Game.Home.Names.Short, game.Game.StartDate, formattedTime)
|
||||||
|
} else {
|
||||||
|
gameInfo = fmt.Sprintf("Upcoming: %s @ %s on %s at %s",
|
||||||
game.Game.Away.Names.Short, game.Game.Home.Names.Short, game.Game.StartDate, game.Game.StartTime)
|
game.Game.Away.Names.Short, game.Game.Home.Names.Short, game.Game.StartDate, game.Game.StartTime)
|
||||||
|
}
|
||||||
} else if game.Game.GameState == "final" {
|
} else if game.Game.GameState == "final" {
|
||||||
gameInfo = fmt.Sprintf("Final: %s: **%s** %s: **%s**",
|
gameInfo = fmt.Sprintf("Final: %s: **%s** %s: **%s**",
|
||||||
game.Game.Away.Names.Short, game.Game.Away.Score,
|
game.Game.Away.Names.Short, game.Game.Away.Score,
|
||||||
|
@@ -6,8 +6,11 @@ import (
|
|||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
"unicode"
|
"unicode"
|
||||||
|
"discord-scores-bot/config"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Game struct {
|
type Game struct {
|
||||||
@@ -15,6 +18,7 @@ type Game struct {
|
|||||||
GameID string `json:"gameID"`
|
GameID string `json:"gameID"`
|
||||||
StartDate string `json:"startDate"`
|
StartDate string `json:"startDate"`
|
||||||
StartTime string `json:"startTime"`
|
StartTime string `json:"startTime"`
|
||||||
|
StartTimeEpoch string `json:"startTimeEpoch"`
|
||||||
GameState string `json:"gameState"`
|
GameState string `json:"gameState"`
|
||||||
CurrentPeriod string `json:"currentPeriod"`
|
CurrentPeriod string `json:"currentPeriod"`
|
||||||
ContestClock string `json:"contestClock"`
|
ContestClock string `json:"contestClock"`
|
||||||
@@ -33,9 +37,15 @@ type Team struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func GetGameInfo(teamName string) string {
|
func GetGameInfo(teamName string) string {
|
||||||
teamName = strings.TrimSpace(teamName)
|
teamName = strings.TrimSpace(strings.ToLower(teamName))
|
||||||
week := ""
|
week := ""
|
||||||
|
|
||||||
|
// Check if the teamName matches a custom name and replace if found
|
||||||
|
if customName, exists := config.CustomTeamNames[teamName]; exists {
|
||||||
|
log.Printf("Custom team name detected: %s -> %s", teamName, customName)
|
||||||
|
teamName = customName
|
||||||
|
}
|
||||||
|
|
||||||
// Check if the last two characters of the input are integers for the week
|
// Check if the last two characters of the input are integers for the week
|
||||||
if len(teamName) >= 2 && unicode.IsDigit(rune(teamName[len(teamName)-1])) && unicode.IsDigit(rune(teamName[len(teamName)-2])) {
|
if len(teamName) >= 2 && unicode.IsDigit(rune(teamName[len(teamName)-1])) && unicode.IsDigit(rune(teamName[len(teamName)-2])) {
|
||||||
week = teamName[len(teamName)-2:]
|
week = teamName[len(teamName)-2:]
|
||||||
@@ -90,8 +100,13 @@ func GetGameInfo(teamName string) string {
|
|||||||
game.Game.Home.Names.Short, game.Game.Home.Score,
|
game.Game.Home.Names.Short, game.Game.Home.Score,
|
||||||
period, game.Game.ContestClock)
|
period, game.Game.ContestClock)
|
||||||
} else if game.Game.GameState == "pre" {
|
} else if game.Game.GameState == "pre" {
|
||||||
gameInfo = fmt.Sprintf("Upcoming: %s @ %s on %s at %s ET",
|
centralTime, err := convertEpochToCentralTime(game.Game.StartTimeEpoch)
|
||||||
game.Game.Away.Names.Short, game.Game.Home.Names.Short, game.Game.StartDate, game.Game.StartTime)
|
if err != nil {
|
||||||
|
log.Printf("Time conversion error: %v", err)
|
||||||
|
centralTime = game.Game.StartTime // Fall back to original time if conversion fails
|
||||||
|
}
|
||||||
|
gameInfo = fmt.Sprintf("Upcoming: %s @ %s on %s at %s CT",
|
||||||
|
game.Game.Away.Names.Short, game.Game.Home.Names.Short, game.Game.StartDate, centralTime)
|
||||||
} else if game.Game.GameState == "final" {
|
} else if game.Game.GameState == "final" {
|
||||||
gameInfo = fmt.Sprintf("Final: %s: **%s** %s: **%s**",
|
gameInfo = fmt.Sprintf("Final: %s: **%s** %s: **%s**",
|
||||||
game.Game.Away.Names.Short, game.Game.Away.Score,
|
game.Game.Away.Names.Short, game.Game.Away.Score,
|
||||||
@@ -109,3 +124,20 @@ func GetGameInfo(teamName string) string {
|
|||||||
return "No game found for the specified team."
|
return "No game found for the specified team."
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func convertEpochToCentralTime(epochStr string) (string, error) {
|
||||||
|
epoch, err := strconv.ParseInt(epochStr, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("error parsing epoch: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert the epoch time to time.Time and set it to Central Time
|
||||||
|
t := time.Unix(epoch, 0).UTC()
|
||||||
|
locCT, err := time.LoadLocation("America/Chicago")
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("error loading Central Time location: %w", err)
|
||||||
|
}
|
||||||
|
centralTime := t.In(locCT)
|
||||||
|
|
||||||
|
return centralTime.Format("03:04 PM"), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user