From edf72fe2891b4736adecdd0a680c95dfc48e58e6 Mon Sep 17 00:00:00 2001 From: ION606 Date: Sun, 23 Mar 2025 13:38:56 -0400 Subject: [PATCH] initial code commit --- .gitignore | 1 + Bot/Commands/boop.go | 24 ++++++++++++++ Bot/Commands/catfact.go | 61 ++++++++++++++++++++++++++++++++++ Bot/Commands/cuddle.go | 18 ++++++++++ Bot/Commands/hug.go | 18 ++++++++++ Bot/Commands/meow.go | 63 +++++++++++++++++++++++++++++++++++ Bot/Commands/purr.go | 18 ++++++++++ Bot/Commands/snuggle.go | 18 ++++++++++ Bot/Helpers/showError.go | 18 ++++++++++ Bot/bot.go | 72 ++++++++++++++++++++++++++++++++++++++++ Bot/register.go | 33 ++++++++++++++++++ Dockerfile | 16 +++++++++ Makefile | 5 +++ go.mod | 11 ++++++ go.sum | 28 ++++++++++++++++ main.go | 23 +++++++++++++ 16 files changed, 427 insertions(+) create mode 100644 Bot/Commands/boop.go create mode 100644 Bot/Commands/catfact.go create mode 100644 Bot/Commands/cuddle.go create mode 100644 Bot/Commands/hug.go create mode 100644 Bot/Commands/meow.go create mode 100644 Bot/Commands/purr.go create mode 100644 Bot/Commands/snuggle.go create mode 100644 Bot/Helpers/showError.go create mode 100644 Bot/bot.go create mode 100644 Bot/register.go create mode 100644 Dockerfile create mode 100644 Makefile create mode 100644 go.mod create mode 100644 go.sum create mode 100644 main.go diff --git a/.gitignore b/.gitignore index adf8f72..d43a39d 100644 --- a/.gitignore +++ b/.gitignore @@ -21,3 +21,4 @@ # Go workspace file go.work +.env diff --git a/Bot/Commands/boop.go b/Bot/Commands/boop.go new file mode 100644 index 0000000..36f9eed --- /dev/null +++ b/Bot/Commands/boop.go @@ -0,0 +1,24 @@ +package commands + +import "github.com/bwmarrin/discordgo" + +var BoopCommand = &discordgo.ApplicationCommand{ + Name: "boop", + Description: "Say Boop!", +} + +func HandleBoop(s *discordgo.Session, i *discordgo.InteractionCreate) { + var username string + if i.Member != nil { + username = i.Member.User.Username + } else if i.User != nil { + username = i.User.Username + } + response := &discordgo.InteractionResponse{ + Type: discordgo.InteractionResponseChannelMessageWithSource, + Data: &discordgo.InteractionResponseData{ + Content: username + ", Boop!", + }, + } + s.InteractionRespond(i.Interaction, response) +} diff --git a/Bot/Commands/catfact.go b/Bot/Commands/catfact.go new file mode 100644 index 0000000..4f194f2 --- /dev/null +++ b/Bot/Commands/catfact.go @@ -0,0 +1,61 @@ +package commands + +import ( + "encoding/json" + "io/ioutil" + "math/rand" + "net/http" + "time" + + helpers "ion606_bot/Bot/Helpers" + + "github.com/bwmarrin/discordgo" +) + +var CatfactCommand = &discordgo.ApplicationCommand{ + Name: "catfact", + Description: "Fetches a random cat fact", +} + +type CatFactAPIResponse struct { + Facts []struct { + FactNumber int `json:"fact_number"` + Fact string `json:"fact"` + } `json:"facts"` +} + +func HandleCatfact(s *discordgo.Session, i *discordgo.InteractionCreate) { + client := http.Client{ + Timeout: 10 * time.Second, + } + resp, err := client.Get("https://www.catfacts.net/api/") + if err != nil { + helpers.HandleError(s, i, err) + return + } + + defer resp.Body.Close() + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + helpers.HandleError(s, i, err) + return + } + + var result CatFactAPIResponse + err = json.Unmarshal(body, &result) + if err != nil || len(result.Facts) == 0 { + helpers.HandleError(s, i, err) + return + } + + // Pick a random fact from the array. + rand.Seed(time.Now().UnixNano()) + randomFact := result.Facts[rand.Intn(len(result.Facts))].Fact + + s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ + Type: discordgo.InteractionResponseChannelMessageWithSource, + Data: &discordgo.InteractionResponseData{ + Content: randomFact, + }, + }) +} diff --git a/Bot/Commands/cuddle.go b/Bot/Commands/cuddle.go new file mode 100644 index 0000000..82e8e19 --- /dev/null +++ b/Bot/Commands/cuddle.go @@ -0,0 +1,18 @@ +package commands + +import "github.com/bwmarrin/discordgo" + +var CuddleCommand = &discordgo.ApplicationCommand{ + Name: "cuddle", + Description: "Gently cuddle for your day!", +} + +func HandleCuddle(s *discordgo.Session, i *discordgo.InteractionCreate) { + response := &discordgo.InteractionResponse{ + Type: discordgo.InteractionResponseChannelMessageWithSource, + Data: &discordgo.InteractionResponseData{ + Content: "Here's a gentle cuddle for your day! 💕", + }, + } + s.InteractionRespond(i.Interaction, response) +} diff --git a/Bot/Commands/hug.go b/Bot/Commands/hug.go new file mode 100644 index 0000000..107707a --- /dev/null +++ b/Bot/Commands/hug.go @@ -0,0 +1,18 @@ +package commands + +import "github.com/bwmarrin/discordgo" + +var HugCommand = &discordgo.ApplicationCommand{ + Name: "hug", + Description: "Sends you a big warm hug!", +} + +func HandleHug(s *discordgo.Session, i *discordgo.InteractionCreate) { + response := &discordgo.InteractionResponse{ + Type: discordgo.InteractionResponseChannelMessageWithSource, + Data: &discordgo.InteractionResponseData{ + Content: "Sending you a big warm hug! 🤗", + }, + } + s.InteractionRespond(i.Interaction, response) +} diff --git a/Bot/Commands/meow.go b/Bot/Commands/meow.go new file mode 100644 index 0000000..8886047 --- /dev/null +++ b/Bot/Commands/meow.go @@ -0,0 +1,63 @@ +package commands + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "net/http" + "time" + + Helpers "ion606_bot/Bot/Helpers" + + "github.com/bwmarrin/discordgo" +) + +var MeowCommand = &discordgo.ApplicationCommand{ + Name: "meow", + Description: "Sends a random cat image", +} + +type SefinekAPIResponse struct { + Success bool `json:"success"` + Status int `json:"status"` + Info struct { + Category string `json:"category"` + Endpoint string `json:"endpoint"` + } `json:"info"` + Message string `json:"message"` +} + +func HandleMeow(s *discordgo.Session, i *discordgo.InteractionCreate) { + client := http.Client{ + Timeout: 10 * time.Second, + } + + resp, err := client.Get("https://api.sefinek.net/api/v2/random/animal/cat") + if err != nil { + Helpers.HandleError(s, i, fmt.Errorf("fetching cat image: %w", err)) + return + } + defer resp.Body.Close() + + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + Helpers.HandleError(s, i, fmt.Errorf("reading cat image response: %w", err)) + return + } + + var apiResp SefinekAPIResponse + err = json.Unmarshal(body, &apiResp) + if err != nil || !apiResp.Success { + Helpers.HandleError(s, i, fmt.Errorf("parsing cat image response")) + return + } + + // Use the "message" field which contains the image URL. + imageURL := apiResp.Message + s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ + Type: discordgo.InteractionResponseChannelMessageWithSource, + Data: &discordgo.InteractionResponseData{ + Content: imageURL, + }, + }) +} diff --git a/Bot/Commands/purr.go b/Bot/Commands/purr.go new file mode 100644 index 0000000..6a95757 --- /dev/null +++ b/Bot/Commands/purr.go @@ -0,0 +1,18 @@ +package commands + +import "github.com/bwmarrin/discordgo" + +var PurrCommand = &discordgo.ApplicationCommand{ + Name: "purr", + Description: "Purr...", +} + +func HandlePurr(s *discordgo.Session, i *discordgo.InteractionCreate) { + response := &discordgo.InteractionResponse{ + Type: discordgo.InteractionResponseChannelMessageWithSource, + Data: &discordgo.InteractionResponseData{ + Content: "Purr...", + }, + } + s.InteractionRespond(i.Interaction, response) +} diff --git a/Bot/Commands/snuggle.go b/Bot/Commands/snuggle.go new file mode 100644 index 0000000..2c7c3e8 --- /dev/null +++ b/Bot/Commands/snuggle.go @@ -0,0 +1,18 @@ +package commands + +import "github.com/bwmarrin/discordgo" + +var SnuggleCommand = &discordgo.ApplicationCommand{ + Name: "snuggle", + Description: "Time to snuggle up and feel cozy!", +} + +func HandleSnuggle(s *discordgo.Session, i *discordgo.InteractionCreate) { + response := &discordgo.InteractionResponse{ + Type: discordgo.InteractionResponseChannelMessageWithSource, + Data: &discordgo.InteractionResponseData{ + Content: "Time to snuggle up and feel cozy! 🥰", + }, + } + s.InteractionRespond(i.Interaction, response) +} diff --git a/Bot/Helpers/showError.go b/Bot/Helpers/showError.go new file mode 100644 index 0000000..2e60201 --- /dev/null +++ b/Bot/Helpers/showError.go @@ -0,0 +1,18 @@ +package helpers + +import ( + "fmt" + + "github.com/bwmarrin/discordgo" +) + +// HandleError sends an error response including the error text and an emoji. +func HandleError(s *discordgo.Session, i *discordgo.InteractionCreate, err error) { + content := fmt.Sprintf("Error: %s <:error:1353407137421721620>", err.Error()) + s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ + Type: discordgo.InteractionResponseChannelMessageWithSource, + Data: &discordgo.InteractionResponseData{ + Content: content, + }, + }) +} diff --git a/Bot/bot.go b/Bot/bot.go new file mode 100644 index 0000000..463e4d6 --- /dev/null +++ b/Bot/bot.go @@ -0,0 +1,72 @@ +package bot + +import ( + "fmt" + "log" + "os" + "os/signal" + + commands "ion606_bot/Bot/Commands" + + "github.com/bwmarrin/discordgo" +) + +var BotToken string + +// commandHandlers maps command names to their interaction handler functions. +var commandHandlers = map[string]func(*discordgo.Session, *discordgo.InteractionCreate){ + "meow": commands.HandleMeow, + "purr": commands.HandlePurr, + "boop": commands.HandleBoop, + "hug": commands.HandleHug, + "cuddle": commands.HandleCuddle, + "snuggle": commands.HandleSnuggle, + "catfact": commands.HandleCatfact, +} + +// Run starts the Discord bot session and listens for both message and slash command events. +func Run() { + // create a session using the provided bot token. + discord, err := discordgo.New("Bot " + BotToken) + if err != nil { + log.Fatal("Error creating Discord session: ", err) + } + + // add event handlers for messages and interactions. + discord.AddHandler(newMessage) + discord.AddHandler(handleInteractionCreate) + + err = discord.Open() + if err != nil { + log.Fatal("Error opening Discord session: ", err) + } + + RegisterCommands(discord, "") + + fmt.Println("Bot running....") + c := make(chan os.Signal, 1) + signal.Notify(c, os.Interrupt) + <-c + + err = discord.Close() + if err != nil { + log.Println("Error closing Discord session: ", err) + } +} + +func newMessage(s *discordgo.Session, m *discordgo.MessageCreate) { + // prevent the bot from responding to its own messages. + if m.Author.ID == s.State.User.ID { + return + } + // additional message handling logic can go here. +} + +func handleInteractionCreate(s *discordgo.Session, i *discordgo.InteractionCreate) { + name := i.ApplicationCommandData().Name + if handler, found := commandHandlers[name]; found { + handler(s, i) + } else { + log.Printf("No handler found for command: %v", name) + } +} diff --git a/Bot/register.go b/Bot/register.go new file mode 100644 index 0000000..299b820 --- /dev/null +++ b/Bot/register.go @@ -0,0 +1,33 @@ +package bot + +import ( + "log" + + Commands "ion606_bot/Bot/Commands" + helpers "ion606_bot/Bot/Helpers" + + "github.com/bwmarrin/discordgo" +) + +var commandsList = []*discordgo.ApplicationCommand{ + Commands.MeowCommand, + Commands.PurrCommand, + Commands.BoopCommand, + Commands.HugCommand, + Commands.CuddleCommand, + Commands.SnuggleCommand, + Commands.CatfactCommand, +} + +func RegisterCommands(s *discordgo.Session, guildID string) { + for _, cmd := range commandsList { + _, err := s.ApplicationCommandCreate(s.State.User.ID, guildID, cmd) + if err != nil { + log.Printf("Cannot create '%v' command: %v", cmd.Name, err) + // Handle error using HandleError + helpers.HandleError(s, nil, err) + } else { + log.Printf("Registered command: %v", cmd.Name) + } + } +} diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..0393312 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,16 @@ +FROM golang:1.24-alpine AS builder +WORKDIR /app + +# Copy go.mod and go.sum to download dependencies +COPY go.mod go.sum ./ +RUN go mod download + +# Copy the rest of the code and build the bot binary +COPY . . +RUN CGO_ENABLED=0 GOOS=linux go build -o bot . + +FROM alpine:latest +WORKDIR /app +COPY --from=builder /app/bot . + +CMD ["./bot"] \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..d9c7467 --- /dev/null +++ b/Makefile @@ -0,0 +1,5 @@ +build: + docker build -t ion606-bot . + +run: + docker run --rm -d --env-file .env ion606-bot \ No newline at end of file diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..71554d0 --- /dev/null +++ b/go.mod @@ -0,0 +1,11 @@ +module ion606_bot + +go 1.24.1 + +require ( + github.com/bwmarrin/discordgo v0.28.1 // indirect + github.com/gorilla/websocket v1.4.2 // indirect + github.com/joho/godotenv v1.5.1 // 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..2039ab8 --- /dev/null +++ b/go.sum @@ -0,0 +1,28 @@ +github.com/bwmarrin/discordgo v0.28.1 h1:gXsuo2GBO7NbR6uqmrrBDplPUx2T3nzu775q/Rd1aG4= +github.com/bwmarrin/discordgo v0.28.1/go.mod h1:NJZpH+1AfhIcyQsPeuBKsUtYrRnjkyu0kIVMCHkZtRY= +github.com/disgoorg/disgo v0.18.15 h1:T24I/NdUUody4FDvb8YkhSxHtsgRKD8Ui5Vi5PXnIrQ= +github.com/disgoorg/disgo v0.18.15/go.mod h1:dXYVH059d6aK7mI+Nh/3svSRWedNd09P7C2VX3RqbJY= +github.com/disgoorg/json v1.2.0 h1:6e/j4BCfSHIvucG1cd7tJPAOp1RgnnMFSqkvZUtEd1Y= +github.com/disgoorg/json v1.2.0/go.mod h1:BHDwdde0rpQFDVsRLKhma6Y7fTbQKub/zdGO5O9NqqA= +github.com/disgoorg/snowflake/v2 v2.0.3 h1:3B+PpFjr7j4ad7oeJu4RlQ+nYOTadsKapJIzgvSI2Ro= +github.com/disgoorg/snowflake/v2 v2.0.3/go.mod h1:W6r7NUA7DwfZLwr00km6G4UnZ0zcoLBRufhkFWgAc4c= +github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= +github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= +github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= +github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +github.com/sasha-s/go-csync v0.0.0-20240107134140-fcbab37b09ad h1:qIQkSlF5vAUHxEmTbaqt1hkJ/t6skqEGYiMag343ucI= +github.com/sasha-s/go-csync v0.0.0-20240107134140-fcbab37b09ad/go.mod h1:/pA7k3zsXKdjjAiUhB5CjuKib9KJGCaLvZwtxGC8U0s= +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/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= +golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= +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/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= +golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +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..457b860 --- /dev/null +++ b/main.go @@ -0,0 +1,23 @@ +package main + +import ( + "log" + "os" + + bot "ion606_bot/Bot" + + "github.com/joho/godotenv" +) + +func main() { + // Attempt to load .env, but don't fail if not found. + _ = godotenv.Load() + + // Get the bot token from the environment + bot.BotToken = os.Getenv("BOT_TOKEN") + if bot.BotToken == "" { + log.Fatal("BOT_TOKEN is not set in the environment") + } + + bot.Run() +}