initial
This commit is contained in:
commit
1a4986f294
18 changed files with 3181 additions and 0 deletions
222
pkg/config/config.go
Normal file
222
pkg/config/config.go
Normal file
|
@ -0,0 +1,222 @@
|
|||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/joho/godotenv"
|
||||
"log/slog"
|
||||
)
|
||||
|
||||
const (
|
||||
// Environment variable prefix for the application
|
||||
envPrefix = "JUKEBOX_"
|
||||
)
|
||||
|
||||
// Config holds all the configuration for the application
|
||||
type Config struct {
|
||||
// Discord configuration
|
||||
DiscordToken string
|
||||
DiscordGuildID string
|
||||
DiscordChannelID string
|
||||
|
||||
// Subsonic configuration
|
||||
SubsonicServer string
|
||||
SubsonicUsername string
|
||||
SubsonicPassword string
|
||||
SubsonicVersion string
|
||||
|
||||
// Jukebox configuration
|
||||
AudioVolume float64
|
||||
TimeoutSec int
|
||||
}
|
||||
|
||||
// Load loads the configuration from environment variables and .env file
|
||||
func Load() (*Config, error) {
|
||||
// Try to load .env file
|
||||
if err := godotenv.Load(); err != nil {
|
||||
slog.Info("No .env file found, using environment variables only")
|
||||
} else {
|
||||
slog.Info("Loaded environment variables from .env file")
|
||||
}
|
||||
|
||||
// Load the raw values first
|
||||
rawGuildID := getEnv(envPrefix+"DISCORD_GUILD_ID", "")
|
||||
|
||||
// Clean up Guild ID - ensure we have just the snowflake
|
||||
cleanGuildID := cleanSnowflake(rawGuildID)
|
||||
if cleanGuildID != rawGuildID && rawGuildID != "" {
|
||||
slog.Warn("Cleaned Discord Guild ID", "original", rawGuildID, "cleaned", cleanGuildID)
|
||||
}
|
||||
|
||||
config := &Config{
|
||||
// Discord
|
||||
DiscordToken: getEnv(envPrefix+"DISCORD_TOKEN", ""),
|
||||
DiscordGuildID: cleanGuildID,
|
||||
DiscordChannelID: getEnv(envPrefix+"DISCORD_CHANNEL_ID", ""),
|
||||
|
||||
// Subsonic
|
||||
SubsonicServer: getEnv(envPrefix+"SUBSONIC_SERVER", ""),
|
||||
SubsonicUsername: getEnv(envPrefix+"SUBSONIC_USERNAME", ""),
|
||||
SubsonicPassword: getEnv(envPrefix+"SUBSONIC_PASSWORD", ""),
|
||||
SubsonicVersion: getEnv(envPrefix+"SUBSONIC_VERSION", "1.16.1"),
|
||||
|
||||
// Jukebox
|
||||
AudioVolume: getEnvFloat(envPrefix+"AUDIO_VOLUME", 0.5),
|
||||
TimeoutSec: getEnvInt(envPrefix+"TIMEOUT_SEC", 30),
|
||||
}
|
||||
|
||||
if err := config.validate(); err != nil {
|
||||
// Print helpful debug information
|
||||
PrintDebugInfo()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return config, nil
|
||||
}
|
||||
|
||||
// validate checks if the required configuration values are set
|
||||
func (c *Config) validate() error {
|
||||
if c.DiscordToken == "" {
|
||||
return errorMissingConfig("discord token")
|
||||
}
|
||||
|
||||
if c.SubsonicServer == "" {
|
||||
return errorMissingConfig("subsonic server")
|
||||
}
|
||||
|
||||
if c.SubsonicUsername == "" {
|
||||
return errorMissingConfig("subsonic username")
|
||||
}
|
||||
|
||||
if c.SubsonicPassword == "" {
|
||||
return errorMissingConfig("subsonic password")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// errorMissingConfig returns a formatted error for missing configuration
|
||||
func errorMissingConfig(name string) error {
|
||||
return fmt.Errorf("%s is required", name)
|
||||
}
|
||||
|
||||
// GetTimeout returns the timeout duration
|
||||
func (c *Config) GetTimeout() time.Duration {
|
||||
return time.Duration(c.TimeoutSec) * time.Second
|
||||
}
|
||||
|
||||
// getEnv gets an environment variable or returns a default value
|
||||
func getEnv(key, defaultValue string) string {
|
||||
if value, exists := os.LookupEnv(key); exists {
|
||||
return value
|
||||
}
|
||||
return defaultValue
|
||||
}
|
||||
|
||||
// getEnvInt gets an environment variable as an integer or returns a default value
|
||||
func getEnvInt(key string, defaultValue int) int {
|
||||
if value, exists := os.LookupEnv(key); exists {
|
||||
if intValue, err := strconv.Atoi(value); err == nil {
|
||||
return intValue
|
||||
}
|
||||
}
|
||||
return defaultValue
|
||||
}
|
||||
|
||||
// getEnvFloat gets an environment variable as a float or returns a default value
|
||||
func getEnvFloat(key string, defaultValue float64) float64 {
|
||||
if value, exists := os.LookupEnv(key); exists {
|
||||
if floatValue, err := strconv.ParseFloat(value, 64); err == nil {
|
||||
return floatValue
|
||||
}
|
||||
}
|
||||
return defaultValue
|
||||
}
|
||||
|
||||
// PrintDebugInfo prints helpful debugging information for environment variables
|
||||
func PrintDebugInfo() {
|
||||
slog.Info("=== Configuration Debug Information ===")
|
||||
slog.Info("Required environment variables:")
|
||||
checkEnvVar(envPrefix + "DISCORD_TOKEN")
|
||||
checkEnvVar(envPrefix + "DISCORD_GUILD_ID")
|
||||
checkEnvVar(envPrefix + "SUBSONIC_SERVER")
|
||||
checkEnvVar(envPrefix + "SUBSONIC_USERNAME")
|
||||
checkEnvVar(envPrefix + "SUBSONIC_PASSWORD")
|
||||
|
||||
slog.Info("Optional environment variables:")
|
||||
checkEnvVar(envPrefix + "DISCORD_CHANNEL_ID")
|
||||
checkEnvVar(envPrefix + "SUBSONIC_VERSION")
|
||||
checkEnvVar(envPrefix + "AUDIO_VOLUME")
|
||||
checkEnvVar(envPrefix + "TIMEOUT_SEC")
|
||||
|
||||
slog.Info("Troubleshooting tips:")
|
||||
slog.Info("1. Your .env file is in the correct directory")
|
||||
slog.Info("2. All variable names have the JUKEBOX_ prefix")
|
||||
slog.Info("3. There are no spaces around the = sign in your .env file")
|
||||
slog.Info(" Example: JUKEBOX_DISCORD_TOKEN=your_token_here")
|
||||
slog.Info("4. Your .env file has been loaded (check for errors above)")
|
||||
slog.Info("===================================")
|
||||
}
|
||||
|
||||
// checkEnvVar checks if an environment variable is set and prints its status
|
||||
func checkEnvVar(name string) {
|
||||
if value, exists := os.LookupEnv(name); exists {
|
||||
// Mask sensitive values
|
||||
displayValue := value
|
||||
if contains(name, "TOKEN", "PASSWORD") {
|
||||
if len(displayValue) > 8 {
|
||||
displayValue = displayValue[:4] + "..." + displayValue[len(displayValue)-4:]
|
||||
} else {
|
||||
displayValue = "****"
|
||||
}
|
||||
}
|
||||
|
||||
// Special handling for Guild ID to detect common issues
|
||||
if strings.Contains(name, "GUILD_ID") && strings.Contains(value, "=") {
|
||||
slog.Warn("Environment variable contains equals sign",
|
||||
"name", name,
|
||||
"raw_value", displayValue,
|
||||
"cleaned_value", cleanSnowflake(value))
|
||||
} else {
|
||||
slog.Info("Environment variable check", "name", name, "value", displayValue, "status", "set")
|
||||
}
|
||||
} else {
|
||||
slog.Warn("Environment variable not set", "name", name)
|
||||
}
|
||||
}
|
||||
|
||||
// contains checks if the string contains any of the substrings
|
||||
func contains(str string, substrings ...string) bool {
|
||||
for _, substring := range substrings {
|
||||
if strings.Contains(str, substring) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// cleanSnowflake cleans a Discord snowflake (ID) from potential contamination
|
||||
// This handles cases where the ID might include the variable name or other text
|
||||
func cleanSnowflake(input string) string {
|
||||
// If the input contains an equals sign, it might be contaminated
|
||||
if strings.Contains(input, "=") {
|
||||
parts := strings.SplitN(input, "=", 2)
|
||||
if len(parts) == 2 {
|
||||
input = parts[1] // Take the part after the equals sign
|
||||
}
|
||||
}
|
||||
|
||||
// Extract only digits
|
||||
var result strings.Builder
|
||||
for _, char := range input {
|
||||
if char >= '0' && char <= '9' {
|
||||
result.WriteRune(char)
|
||||
}
|
||||
}
|
||||
|
||||
return result.String()
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue