Begin Go rewrite: remove Python, scaffold module and config
Pivot from FastAPI web portal to a Matrix command bot (modeled on Pete).
Users will issue !movie / !tv / !music commands in allowlisted rooms; the
bot performs a top-hit search against Radarr/Sonarr/Lidarr and adds it.
This commit is session 1 of a multi-session rewrite (see SESSION_PLAN.md):
- Delete app/, requirements.txt, old Dockerfile, .env.example
- Add go.mod (mautrix-go + yaml.v3)
- Add internal/config: YAML loader with ${ENV} expansion, validates
matrix creds, allowed_rooms, and per-service *arr config
- Reset .gitignore / .dockerignore for the Go layout
This commit is contained in:
127
internal/config/config.go
Normal file
127
internal/config/config.go
Normal file
@@ -0,0 +1,127 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"os"
|
||||
"regexp"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
var envBracketRe = regexp.MustCompile(`\$\{([^}]+)\}`)
|
||||
|
||||
type Config struct {
|
||||
Matrix MatrixConfig `yaml:"matrix"`
|
||||
Services ServicesConfig `yaml:"services"`
|
||||
}
|
||||
|
||||
type MatrixConfig struct {
|
||||
Homeserver string `yaml:"homeserver"`
|
||||
UserID string `yaml:"user_id"`
|
||||
Password string `yaml:"password"`
|
||||
PickleKey string `yaml:"pickle_key"`
|
||||
DisplayName string `yaml:"display_name"`
|
||||
DataDir string `yaml:"data_dir"`
|
||||
CommandPrefix string `yaml:"command_prefix"`
|
||||
// AllowedRooms is the set of room IDs the bot listens for commands in.
|
||||
// Messages in any other room are ignored.
|
||||
AllowedRooms []string `yaml:"allowed_rooms"`
|
||||
}
|
||||
|
||||
type ServicesConfig struct {
|
||||
Radarr *ArrConfig `yaml:"radarr"`
|
||||
Sonarr *ArrConfig `yaml:"sonarr"`
|
||||
Lidarr *ArrConfig `yaml:"lidarr"`
|
||||
}
|
||||
|
||||
type ArrConfig struct {
|
||||
URL string `yaml:"url"`
|
||||
APIKey string `yaml:"api_key"`
|
||||
QualityProfileID int `yaml:"quality_profile_id"`
|
||||
RootFolder string `yaml:"root_folder"`
|
||||
}
|
||||
|
||||
func Load(path string) (*Config, error) {
|
||||
data, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("read config: %w", err)
|
||||
}
|
||||
|
||||
expanded := envBracketRe.ReplaceAllStringFunc(string(data), func(match string) string {
|
||||
varName := match[2 : len(match)-1]
|
||||
val := os.Getenv(varName)
|
||||
if val == "" {
|
||||
slog.Warn("config: env var referenced but not set", "var", varName)
|
||||
}
|
||||
return val
|
||||
})
|
||||
|
||||
var cfg Config
|
||||
if err := yaml.Unmarshal([]byte(expanded), &cfg); err != nil {
|
||||
return nil, fmt.Errorf("parse config: %w", err)
|
||||
}
|
||||
|
||||
cfg.applyDefaults()
|
||||
|
||||
if err := cfg.validate(); err != nil {
|
||||
return nil, fmt.Errorf("validate config: %w", err)
|
||||
}
|
||||
|
||||
return &cfg, nil
|
||||
}
|
||||
|
||||
func (c *Config) validate() error {
|
||||
if c.Matrix.Homeserver == "" {
|
||||
return fmt.Errorf("matrix.homeserver is required")
|
||||
}
|
||||
if c.Matrix.UserID == "" {
|
||||
return fmt.Errorf("matrix.user_id is required")
|
||||
}
|
||||
if c.Matrix.Password == "" {
|
||||
return fmt.Errorf("matrix.password is required")
|
||||
}
|
||||
if len(c.Matrix.AllowedRooms) == 0 {
|
||||
return fmt.Errorf("matrix.allowed_rooms must have at least one entry")
|
||||
}
|
||||
if c.Services.Radarr == nil && c.Services.Sonarr == nil && c.Services.Lidarr == nil {
|
||||
return fmt.Errorf("at least one of services.radarr/sonarr/lidarr must be configured")
|
||||
}
|
||||
for name, svc := range map[string]*ArrConfig{
|
||||
"radarr": c.Services.Radarr,
|
||||
"sonarr": c.Services.Sonarr,
|
||||
"lidarr": c.Services.Lidarr,
|
||||
} {
|
||||
if svc == nil {
|
||||
continue
|
||||
}
|
||||
if svc.URL == "" {
|
||||
return fmt.Errorf("services.%s.url is required when service is set", name)
|
||||
}
|
||||
if svc.APIKey == "" {
|
||||
return fmt.Errorf("services.%s.api_key is required when service is set", name)
|
||||
}
|
||||
if svc.RootFolder == "" {
|
||||
return fmt.Errorf("services.%s.root_folder is required when service is set", name)
|
||||
}
|
||||
if svc.QualityProfileID == 0 {
|
||||
return fmt.Errorf("services.%s.quality_profile_id is required when service is set", name)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Config) applyDefaults() {
|
||||
if c.Matrix.DataDir == "" {
|
||||
c.Matrix.DataDir = "./data"
|
||||
}
|
||||
if c.Matrix.DisplayName == "" {
|
||||
c.Matrix.DisplayName = "Bellhop"
|
||||
}
|
||||
if c.Matrix.PickleKey == "" {
|
||||
c.Matrix.PickleKey = "bellhop_pickle_key"
|
||||
}
|
||||
if c.Matrix.CommandPrefix == "" {
|
||||
c.Matrix.CommandPrefix = "!"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user