summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.env.example6
-rw-r--r--.gitignore32
-rw-r--r--Makefile45
-rw-r--r--ai/main.go12
-rw-r--r--config/config.go90
-rw-r--r--go.mod5
-rw-r--r--go.sum2
-rw-r--r--types/bot.go19
-rw-r--r--types/logger.go34
-rw-r--r--utils/logger/logger.go99
10 files changed, 344 insertions, 0 deletions
diff --git a/.env.example b/.env.example
new file mode 100644
index 0000000..5e9f24c
--- /dev/null
+++ b/.env.example
@@ -0,0 +1,6 @@
+DISCORD_TOKEN=
+YOUTUBE_API_KEY=
+SPOTIFY_CLIENT_ID=
+SPOTIFY_CLIENT_SECRET=
+ACTIVITY= # Activity Type is of type int, 1: Playing, 2: Listening, 3: Watching, 4: Streaming
+ACTIVITY_MESSAGE= \ No newline at end of file
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..91aead3
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,32 @@
+# Binaries for programs and plugins
+*.exe
+*.exe~
+*.dll
+*.so
+*.dylib
+
+# Test binary, built with `go test -c`
+*.test
+
+# Output of the go coverage tool, specifically when used with LiteIDE
+*.out
+
+# Dependency directories (remove the comment below to include it)
+# vendor/
+
+# Go workspace file
+go.work
+
+# OS Specific
+.DS_Store
+Thumbs.db
+
+# IDE
+.idea
+.vscode
+
+# Build files
+bin/
+
+# Environment variables
+.env
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..8417dbf
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,45 @@
+BINARY_NAME=ai
+BUILD_PATH=bin/$(BINARY_NAME)
+MAIN_PATH=$(BINARY_NAME)/main.go
+ENV_PATH=.env
+
+.PHONY: setup clean build run dev all
+
+define ensure_setup
+ @if [ ! -f $(ENV_PATH) ]; then \
+ echo "Running setup first..."; \
+ $(MAKE) -s setup; \
+ fi
+endef
+
+setup:
+ @echo "Setting up environment..."
+ @go mod download
+ @if [ ! -f $(ENV_PATH) ]; then cp .env.example $(ENV_PATH); fi
+ @echo "Environment setup complete."
+
+clean:
+ @echo "Cleaning up..."
+ @rm -rf bin
+ @echo "Cleanup complete."
+
+build:
+ $(call ensure_setup)
+ @echo "Building..."
+ @go build -o $(BUILD_PATH) $(MAIN_PATH) || true
+ @echo "Build complete."
+
+run:
+ $(call ensure_setup)
+ @if [ ! -f $(BUILD_PATH) ]; then echo "Binary not found. Building binary..."; $(MAKE) -s build; fi
+ @echo "Running..."
+ @$(BUILD_PATH) || true
+
+dev:
+ $(call ensure_setup)
+ @echo "Running in development mode..."
+ @go run $(MAIN_PATH) || true
+
+all: setup clean build run
+
+.SILENT: \ No newline at end of file
diff --git a/ai/main.go b/ai/main.go
new file mode 100644
index 0000000..433dab2
--- /dev/null
+++ b/ai/main.go
@@ -0,0 +1,12 @@
+package main
+
+import (
+ "ai/config"
+ "ai/types"
+ "ai/utils/logger"
+ "fmt"
+)
+
+func main() {
+ logger.Log(fmt.Sprintf("Bot Started. Config: %+v", config.Config), types.LogOptions{})
+}
diff --git a/config/config.go b/config/config.go
new file mode 100644
index 0000000..e9f08b5
--- /dev/null
+++ b/config/config.go
@@ -0,0 +1,90 @@
+package config
+
+import (
+ "ai/types"
+ "ai/utils/logger"
+ "os"
+ "strconv"
+ "strings"
+
+ "github.com/joho/godotenv"
+)
+
+var Config *types.BotConfig
+
+func init() {
+ logPrefix := "Config"
+ logOptions := types.LogOptions{
+ Timestamp: true,
+ Prefix: logPrefix,
+ Level: types.Error,
+ Fatal: true,
+ }
+
+ if err := godotenv.Load(); err != nil {
+ logger.Log("Failed to load environment variables", logOptions)
+ }
+
+ Config = &types.BotConfig{
+ DiscordToken: getEnv("DISCORD_TOKEN"),
+ SpotifyClientId: getEnv("SPOTIFY_CLIENT_ID"),
+ SpotifyClientSecret: getEnv("SPOTIFY_CLIENT_SECRET"),
+ YoutubeAPIKey: getEnv("YOUTUBE_API_KEY"),
+ Activity: types.ActivityType(getIntEnv("ACTIVITY")),
+ ActivityMessage: getEnv("ACTIVITY_MESSAGE"),
+ }
+
+ if Config.DiscordToken == "" {
+ logger.Log("Unable to read Discord token. environment variable DISCORD_TOKEN is required", logOptions)
+ }
+
+ if Config.SpotifyClientId == "" {
+ logger.Log("Unable to read Spotify client ID. environment variable SPOTIFY_CLIENT_ID is required", logOptions)
+ }
+
+ if Config.SpotifyClientSecret == "" {
+ logger.Log("Unable to read Spotify client secret. environment variable SPOTIFY_CLIENT_SECRET is required", logOptions)
+ }
+
+ if Config.YoutubeAPIKey == "" {
+ logger.Log("Unable to read YouTube API key. environment variable YOUTUBE_API_KEY is required", logOptions)
+ }
+
+ if Config.Activity == 0 {
+ logOptions.Level = types.Warn
+ logOptions.Fatal = false
+ logger.Log("Activity message is empty or not set. Defaulting to PLAYING", logOptions)
+ Config.Activity = types.PLAYING
+ }
+
+ if Config.ActivityMessage == "" {
+ logOptions.Level = types.Warn
+ logOptions.Fatal = false
+ logger.Log("Activity message is empty or not set. Defaulting to empty string", logOptions)
+ Config.ActivityMessage = ""
+ }
+
+ logOptions.Level = types.Success
+ logOptions.Fatal = false
+ logger.Log("Config loaded successfully", logOptions)
+}
+
+func getEnv(key string) string {
+ value, exists := os.LookupEnv(key)
+ if !exists {
+ return ""
+ }
+ return strings.TrimSpace(value)
+}
+
+func getIntEnv(key string) int {
+ value := getEnv(key)
+ if value == "" {
+ return 0
+ }
+ i, err := strconv.Atoi(value)
+ if err != nil {
+ return 0
+ }
+ return i
+}
diff --git a/go.mod b/go.mod
new file mode 100644
index 0000000..5b27d12
--- /dev/null
+++ b/go.mod
@@ -0,0 +1,5 @@
+module ai
+
+go 1.24.1
+
+require github.com/joho/godotenv v1.5.1
diff --git a/go.sum b/go.sum
new file mode 100644
index 0000000..d61b19e
--- /dev/null
+++ b/go.sum
@@ -0,0 +1,2 @@
+github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
+github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
diff --git a/types/bot.go b/types/bot.go
new file mode 100644
index 0000000..417aecc
--- /dev/null
+++ b/types/bot.go
@@ -0,0 +1,19 @@
+package types
+
+type ActivityType int
+
+const (
+ PLAYING ActivityType = iota
+ LISTENING
+ WATCHING
+ STREAMING
+)
+
+type BotConfig struct {
+ DiscordToken string
+ SpotifyClientId string
+ SpotifyClientSecret string
+ YoutubeAPIKey string
+ Activity ActivityType
+ ActivityMessage string
+}
diff --git a/types/logger.go b/types/logger.go
new file mode 100644
index 0000000..a913312
--- /dev/null
+++ b/types/logger.go
@@ -0,0 +1,34 @@
+package types
+
+type LogLevel string
+
+const (
+ Debug LogLevel = "debug"
+ Info LogLevel = "info"
+ Warn LogLevel = "warn"
+ Error LogLevel = "error"
+ Success LogLevel = "success"
+
+ Reset = "\033[0m"
+ Cyan = "\033[36m"
+ Gray = "\033[90m"
+
+ LevelColorInfo = "\033[34mINFO \033[0m"
+ LevelColorWarn = "\033[33mWARN \033[0m"
+ LevelColorError = "\033[31mERROR \033[0m"
+ LevelColorDebug = "\033[35mDEBUG \033[0m"
+ LevelColorSuccess = "\033[32mSUCCESS\033[0m"
+
+ MessageColorInfo = "\033[97m"
+ MessageColorWarn = "\033[33m"
+ MessageColorError = "\033[31m"
+ MessageColorDebug = "\033[90m"
+ MessageColorSuccess = "\033[32m"
+)
+
+type LogOptions struct {
+ Timestamp bool
+ Prefix string
+ Level LogLevel
+ Fatal bool
+}
diff --git a/utils/logger/logger.go b/utils/logger/logger.go
new file mode 100644
index 0000000..87e7772
--- /dev/null
+++ b/utils/logger/logger.go
@@ -0,0 +1,99 @@
+package logger
+
+import (
+ "fmt"
+ "os"
+ "strings"
+ "time"
+
+ "ai/types"
+)
+
+func getTimestamp() string {
+ return time.Now().Format(time.RFC3339)
+}
+
+func getLevelColor(level types.LogLevel) string {
+ switch level {
+ case types.Info:
+ return types.LevelColorInfo
+ case types.Warn:
+ return types.LevelColorWarn
+ case types.Error:
+ return types.LevelColorError
+ case types.Debug:
+ return types.LevelColorDebug
+ case types.Success:
+ return types.LevelColorSuccess
+ default:
+ return types.LevelColorInfo
+ }
+}
+
+func getMessageColor(level types.LogLevel) string {
+ switch level {
+ case types.Info:
+ return types.MessageColorInfo
+ case types.Warn:
+ return types.MessageColorWarn
+ case types.Error:
+ return types.MessageColorError
+ case types.Debug:
+ return types.MessageColorDebug
+ case types.Success:
+ return types.MessageColorSuccess
+ default:
+ return types.MessageColorInfo
+ }
+}
+
+func Log(message interface{}, options types.LogOptions) {
+ var builder strings.Builder
+
+ if options.Level == "" {
+ options.Level = types.Info
+ }
+
+ if options.Timestamp {
+ builder.WriteString(types.Gray)
+ builder.WriteString(getTimestamp())
+ builder.WriteString(types.Reset)
+ builder.WriteString(" ")
+ }
+
+ builder.WriteString(getLevelColor(options.Level))
+ builder.WriteString(" ")
+
+ if options.Prefix != "" {
+ builder.WriteString(types.Cyan)
+ builder.WriteString("[")
+ builder.WriteString(options.Prefix)
+ builder.WriteString("]")
+ builder.WriteString(types.Reset)
+ builder.WriteString(" ")
+ }
+
+ builder.WriteString(getMessageColor(options.Level))
+
+ switch msg := message.(type) {
+ case error:
+ builder.WriteString(msg.Error())
+ case string:
+ builder.WriteString(msg)
+ default:
+ builder.WriteString(fmt.Sprintf("%v", msg))
+ }
+
+ builder.WriteString(types.Reset)
+ builder.WriteString("\n")
+
+ if options.Level == types.Error || options.Level == types.Warn {
+ os.Stderr.WriteString(builder.String())
+ } else {
+ os.Stdout.WriteString(builder.String())
+ }
+
+ if options.Fatal {
+ os.Exit(1)
+ }
+}