diff --git a/main.go b/main.go index f76494e..de42767 100644 --- a/main.go +++ b/main.go @@ -1,7 +1,7 @@ package main import ( - "encoding/json" + "database/sql" "fmt" "log" "os" @@ -11,6 +11,7 @@ import ( "time" "github.com/bwmarrin/discordgo" + _ "github.com/mattn/go-sqlite3" // SQLite3-Treiber importieren ) // ===== Defaults / Names (pro Guild per Commands konfigurierbar) ===== @@ -20,53 +21,21 @@ const ( defaultCategory = "Private Räume" ) +// Pfad zur SQLite-Datenbank +var dbPath = func() string { + if v := os.Getenv("DB_PATH"); v != "" { + return v + } + return "guild_config.db" +}() + // Pfad zur Persistenz-Datei (überschreibbar via CONFIG_PATH) -var configPath = func() string { +/*var configPath = func() string { if v := os.Getenv("CONFIG_PATH"); v != "" { return v } return "guild_config.json" -}() - -// Persistenz der Guild-Konfiguration -func loadGuildCfgs() error { - f, err := os.Open(configPath) - if err != nil { - return err - } - defer f.Close() - - var m map[string]*GuildConfig - if err := json.NewDecoder(f).Decode(&m); err != nil { - return err - } - - cfgMu.Lock() - for k, v := range m { - guildCfgs[k] = v - } - cfgMu.Unlock() - return nil -} - -func saveGuildCfgs() error { - tmp := configPath + ".tmp" - f, err := os.Create(tmp) - if err != nil { - return err - } - enc := json.NewEncoder(f) - enc.SetIndent("", " ") - - cfgMu.RLock() - err = enc.Encode(guildCfgs) - cfgMu.RUnlock() - _ = f.Close() - if err != nil { - return err - } - return os.Rename(tmp, configPath) -} +}()*/ // ===== Per-Guild Config (in-memory) ===== type GuildConfig struct { @@ -75,29 +44,121 @@ type GuildConfig struct { TimeoutMin int `json:"timeout_min"` } +var db *sql.DB + +// Initialize DB +func initDB() { + var err error + db, err = sql.Open("sqlite3", dbPath) + if err != nil { + log.Fatalf("Datenbank-Fehler: %v", err) + } + + // Tabelle erstellen, falls sie noch nicht existiert + createTableSQL := `CREATE TABLE IF NOT EXISTS guild_config ( + guild_id TEXT PRIMARY KEY, + lobby_name TEXT, + category_name TEXT, + timeout_min INTEGER + );` + _, err = db.Exec(createTableSQL) + if err != nil { + log.Fatalf("Fehler beim Erstellen der Tabelle: %v", err) + } +} + +// Close DB connection +func closeDB() { + if err := db.Close(); err != nil { + log.Fatalf("Fehler beim Schließen der DB: %v", err) + } +} + +// Laden der Guild-Konfiguration aus der Datenbank +func loadGuildCfgs() error { + rows, err := db.Query("SELECT guild_id, lobby_name, category_name, timeout_min FROM guild_config") + if err != nil { + return err + } + defer rows.Close() + + for rows.Next() { + var guildID, lobbyName, categoryName string + var timeoutMin int + if err := rows.Scan(&guildID, &lobbyName, &categoryName, &timeoutMin); err != nil { + return err + } + guildCfgs[guildID] = &GuildConfig{ + LobbyName: lobbyName, + CategoryName: categoryName, + TimeoutMin: timeoutMin, + } + } + + return nil +} + +// Speichern der Guild-Konfiguration in die Datenbank +func saveGuildCfgs() error { + tx, err := db.Begin() + if err != nil { + return err + } + defer tx.Rollback() + + for guildID, cfg := range guildCfgs { + // UPDATE oder INSERT, je nachdem, ob es bereits eine Konfiguration gibt + _, err := tx.Exec(` + INSERT INTO guild_config (guild_id, lobby_name, category_name, timeout_min) + VALUES (?, ?, ?, ?) + ON CONFLICT(guild_id) + DO UPDATE SET + lobby_name = excluded.lobby_name, + category_name = excluded.category_name, + timeout_min = excluded.timeout_min + `, guildID, cfg.LobbyName, cfg.CategoryName, cfg.TimeoutMin) + if err != nil { + return err + } + } + + // Transaktion abschließen + return tx.Commit() +} + var ( - cfgMu sync.RWMutex - guildCfgs = map[string]*GuildConfig{} - createdCmds = map[string][]*discordgo.ApplicationCommand{} // guildID -> cmds - registerOnce sync.Once + cfgMu sync.Mutex + guildCfgs = map[string]*GuildConfig{} + createdCmds = map[string][]*discordgo.ApplicationCommand{} // guildID -> cmds ) func getCfg(guildID string) *GuildConfig { - cfgMu.RLock() + cfgMu.Lock() c, ok := guildCfgs[guildID] - cfgMu.RUnlock() + cfgMu.Unlock() if ok { return c } - cfgMu.Lock() - defer cfgMu.Unlock() - c = &GuildConfig{ - LobbyName: defaultLobbyName, - CategoryName: defaultCategory, - TimeoutMin: envTimeoutDefault(60), + + // Falls nicht vorhanden, Daten aus DB laden + var guildCfg GuildConfig + err := db.QueryRow("SELECT lobby_name, category_name, timeout_min FROM guild_config WHERE guild_id = ?", guildID).Scan(&guildCfg.LobbyName, &guildCfg.CategoryName, &guildCfg.TimeoutMin) + if err != nil { + if err != sql.ErrNoRows { + log.Printf("Fehler beim Abrufen der Guild-Konfiguration: %v", err) + } + // Fallback-Werte, falls die Guild nicht in der DB existiert + guildCfg = GuildConfig{ + LobbyName: defaultLobbyName, + CategoryName: defaultCategory, + TimeoutMin: envTimeoutDefault(1), + } } - guildCfgs[guildID] = c - return c + + cfgMu.Lock() + guildCfgs[guildID] = &guildCfg + cfgMu.Unlock() + return &guildCfg } func envTimeoutDefault(def int) int { @@ -541,8 +602,8 @@ func onInteractionCreate(_ string) func(s *discordgo.Session, i *discordgo.Inter }) case "settimeout": - // Admin-Check zuerst if !isGuildAdmin(s, guildID, i.User, i.Member) { + log.Println("User is not an admin") _ = s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ Type: discordgo.InteractionResponseChannelMessageWithSource, Data: &discordgo.InteractionResponseData{ @@ -553,7 +614,6 @@ func onInteractionCreate(_ string) func(s *discordgo.Session, i *discordgo.Inter return } - // minutes lesen minutes := int64(getCfg(guildID).TimeoutMin) for _, o := range data.Options { if o.Name == "minutes" { @@ -564,9 +624,12 @@ func onInteractionCreate(_ string) func(s *discordgo.Session, i *discordgo.Inter minutes = 1 } + log.Printf("Setting timeout for %d minutes", minutes) + // speichern cfgMu.Lock() getCfg(guildID).TimeoutMin = int(minutes) + log.Println("1-0815") cfgMu.Unlock() if err := saveGuildCfgs(); err != nil { log.Printf("saveGuildCfgs failed: %v", err) @@ -580,6 +643,8 @@ func onInteractionCreate(_ string) func(s *discordgo.Session, i *discordgo.Inter return } + log.Println("2-0815") + // Antwort <= 3s _ = s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ Type: discordgo.InteractionResponseChannelMessageWithSource, @@ -698,6 +763,7 @@ func onGuildCreate(s *discordgo.Session, g *discordgo.GuildCreate) { cfgMu.Lock() createdCmds[g.ID] = append(createdCmds[g.ID], c) cfgMu.Unlock() + log.Printf("Command-Registrierung in %s", g.Name) } } @@ -753,7 +819,7 @@ var ( } ) -func registerCommandsForGuild(s *discordgo.Session, guildID string) { +/*func registerCommandsForGuild(s *discordgo.Session, guildID string) { appID := s.State.User.ID cmds, err := s.ApplicationCommandBulkOverwrite(appID, guildID, slashCommands) if err != nil { @@ -763,18 +829,21 @@ func registerCommandsForGuild(s *discordgo.Session, guildID string) { cfgMu.Lock() createdCmds[guildID] = cmds cfgMu.Unlock() -} + //fmt.Println(createdCmds) +}*/ // ===== main: Multi-Guild, pro Guild registrieren ===== func main() { + initDB() token := os.Getenv("DISCORD_TOKEN") + token = "MTQwMzg1MTM5NDQ1MjI5MTU4NA.GVi04l.qjraLIbFdi_N49UcSUv_BqK89ihb6xXY648J7A" if token == "" { log.Fatal("Bitte setze DISCORD_TOKEN") } // Persistente Konfiguration laden (optional) if err := loadGuildCfgs(); err != nil { - log.Printf("Hinweis: Konnte %s nicht laden (%v). Starte mit Defaults.", configPath, err) + log.Printf("Hinweis: Konnte %s nicht laden (%v). Starte mit Defaults.", dbPath, err) } s, err := discordgo.New("Bot " + token) @@ -786,16 +855,16 @@ func main() { s.AddHandler(onVoiceStateUpdate) s.AddHandler(onGuildCreate) - s.AddHandler(func(s *discordgo.Session, r *discordgo.Ready) { + /*s.AddHandler(func(s *discordgo.Session, r *discordgo.Ready) { for _, g := range s.State.Guilds { registerCommandsForGuild(s, g.ID) } }) s.AddHandler(func(s *discordgo.Session, g *discordgo.GuildCreate) { registerCommandsForGuild(s, g.ID) - }) + })*/ s.AddHandler(onInteractionCreate("")) - s.AddHandler(func(s *discordgo.Session, r *discordgo.Ready) { + /*s.AddHandler(func(s *discordgo.Session, r *discordgo.Ready) { log.Printf("Eingeloggt als %s", r.User.Username) registerOnce.Do(func() { appID := s.State.User.ID @@ -812,7 +881,7 @@ func main() { } log.Printf("Defaults → Lobby: %q | Kategorie: %q | Timeout: %d min", defaultLobbyName, defaultCategory, envTimeoutDefault(60)) }) - }) + })*/ if err := s.Open(); err != nil { log.Fatalf("Gateway-Start fehlgeschlagen: %v", err) @@ -829,6 +898,8 @@ func main() { for _, c := range cmds { if delErr := s.ApplicationCommandDelete(appID, guildID, c.ID); delErr != nil { log.Printf("Cmd-Delete (%s/%s) fehlgeschlagen: %v", guildID, c.Name, delErr) + } else { + log.Printf("Cmd-Delete (%s/%s) ok", guildID, c.Name) } } }