diff --git a/main.go b/main.go index c3cd2de..9d4e8f2 100644 --- a/main.go +++ b/main.go @@ -2,6 +2,7 @@ package main import ( "database/sql" + "encoding/json" "fmt" "log" "os" @@ -15,7 +16,7 @@ import ( ) // ===== Defaults / Names (pro Guild per Commands konfigurierbar) ===== -const ( +var ( pollInterval = 15 * time.Second defaultLobbyName = "➕ Erstelle privaten Raum" defaultCategory = "Private Räume" @@ -29,19 +30,12 @@ var dbPath = func() string { return "guild_config.db" }() -// Pfad zur Persistenz-Datei (überschreibbar via CONFIG_PATH) -/*var configPath = func() string { - if v := os.Getenv("CONFIG_PATH"); v != "" { - return v - } - return "guild_config.json" -}()*/ - // ===== Per-Guild Config (in-memory) ===== type GuildConfig struct { LobbyName string `json:"lobby_name"` CategoryName string `json:"category_name"` TimeoutMin int `json:"timeout_min"` + Language string `json:"language"` // Neue Eigenschaft für Sprache } var db *sql.DB @@ -50,6 +44,36 @@ var db *sql.DB var guildCfgs sync.Map // Für Guild-Konfigurationen var createdCmds sync.Map // Für erstellte Commands +// Sprache für eine Guild setzen +func setLanguage(guildID, language string) error { + cfg := getCfg(guildID) + cfg.Language = language + return saveGuildCfgs() +} + +// Sprachabruf +func getLanguage(guildID string) string { + cfg := getCfg(guildID) + return cfg.Language +} + +func addLanguageColumnIfNotExists() { + // Überprüfen, ob die Spalte bereits existiert + _, err := db.Exec("ALTER TABLE guild_config ADD COLUMN language TEXT DEFAULT 'de'") + if err != nil && !isColumnAlreadyExistsError(err) { + log.Fatalf("Fehler beim Hinzufügen der 'language' Spalte: %v", err) + } else { + log.Println("Spalte 'language' wurde hinzugefügt oder existiert bereits.") + } +} + +// Hilfsfunktion zur Fehlerbehandlung, um zu überprüfen, ob der Fehler auf eine bereits vorhandene Spalte hinweist +func isColumnAlreadyExistsError(err error) bool { + // Dies ist ein spezifischer Fehlercode für SQLite, der darauf hinweist, dass die Spalte bereits existiert + // Der Fehlercode lautet "sqlite3: table ... already has column ..." (in verschiedenen Varianten) + return err != nil && err.Error() == "SQLITE_ERROR: duplicate column name: language" +} + // Initialize DB func initDB() { var err error @@ -58,12 +82,16 @@ func initDB() { log.Fatalf("Datenbank-Fehler: %v", err) } + // Neue Spalte für die Sprache hinzufügen, wenn sie noch nicht existiert + addLanguageColumnIfNotExists() + // 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 + timeout_min INTEGER, + language TEXT DEFAULT 'de' -- Neue Spalte für Sprache hinzufügen );` _, err = db.Exec(createTableSQL) if err != nil { @@ -78,31 +106,32 @@ func closeDB() { } } -// Laden der Guild-Konfiguration aus der Datenbank +// Laden der Guild-Konfiguration mit Sprache func loadGuildCfgs() error { - rows, err := db.Query("SELECT guild_id, lobby_name, category_name, timeout_min FROM guild_config") + rows, err := db.Query("SELECT guild_id, lobby_name, category_name, timeout_min, language FROM guild_config") if err != nil { return err } defer rows.Close() for rows.Next() { - var guildID, lobbyName, categoryName string + var guildID, lobbyName, categoryName, language string var timeoutMin int - if err := rows.Scan(&guildID, &lobbyName, &categoryName, &timeoutMin); err != nil { + if err := rows.Scan(&guildID, &lobbyName, &categoryName, &timeoutMin, &language); err != nil { return err } guildCfgs.Store(guildID, &GuildConfig{ LobbyName: lobbyName, CategoryName: categoryName, TimeoutMin: timeoutMin, + Language: language, // Sprache laden }) } return nil } -// Speichern der Guild-Konfiguration in die Datenbank +// Speichern der Guild-Konfiguration mit Sprache func saveGuildCfgs() error { tx, err := db.Begin() if err != nil { @@ -115,14 +144,15 @@ func saveGuildCfgs() error { cfg := value.(*GuildConfig) _, err := tx.Exec(` - INSERT INTO guild_config (guild_id, lobby_name, category_name, timeout_min) - VALUES (?, ?, ?, ?) + INSERT INTO guild_config (guild_id, lobby_name, category_name, timeout_min, language) + 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) + timeout_min = excluded.timeout_min, + language = excluded.language -- Sprache speichern + `, guildID, cfg.LobbyName, cfg.CategoryName, cfg.TimeoutMin, cfg.Language) if err != nil { log.Printf("Fehler beim Speichern der Guild-Konfiguration: %v", err) } @@ -139,17 +169,19 @@ func getCfg(guildID string) *GuildConfig { return cfg.(*GuildConfig) } 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) + err := db.QueryRow("SELECT lobby_name, category_name, timeout_min, language FROM guild_config WHERE guild_id = ?", guildID).Scan(&guildCfg.LobbyName, &guildCfg.CategoryName, &guildCfg.TimeoutMin, &guildCfg.Language) if err != nil { if err == sql.ErrNoRows { log.Printf("Guild-Konfiguration für %s nicht gefunden, verwenden der Standardwerte", guildID) } else { log.Printf("Fehler beim Abrufen der Guild-Konfiguration für %s: %v", guildID, err) } + // Standardwerte verwenden guildCfg = GuildConfig{ LobbyName: defaultLobbyName, CategoryName: defaultCategory, TimeoutMin: envTimeoutDefault(1), + Language: "de", // Standard-Sprache ist Deutsch } } guildCfgs.Store(guildID, &guildCfg) @@ -619,7 +651,6 @@ func onInteractionCreate(_ string) func(s *discordgo.Session, i *discordgo.Inter // speichern getCfg(guildID).TimeoutMin = int(minutes) - log.Println("1-0815") if err := saveGuildCfgs(); err != nil { log.Printf("saveGuildCfgs failed: %v", err) _ = s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ @@ -632,8 +663,6 @@ 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, @@ -735,6 +764,59 @@ func onInteractionCreate(_ string) func(s *discordgo.Session, i *discordgo.Inter }, }) + case "setlanguage": + // Admin-Check zuerst + if !isGuildAdmin(s, guildID, i.User, i.Member) { + _ = s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ + Type: discordgo.InteractionResponseChannelMessageWithSource, + Data: &discordgo.InteractionResponseData{ + Content: "❌ Nur Administratoren dürfen das.", + Flags: discordgo.MessageFlagsEphemeral, + }, + }) + return + } + + // Option "language" lesen + var language string + for _, o := range data.Options { + if o.Name == "language" { + language = o.StringValue() + break + } + } + if language == "" { + _ = s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ + Type: discordgo.InteractionResponseChannelMessageWithSource, + Data: &discordgo.InteractionResponseData{ + Content: "Bitte gib eine gültige Sprache an (z.B. 'de', 'en').", + Flags: discordgo.MessageFlagsEphemeral, + }, + }) + return + } + + // Speichern + if err := setLanguage(guildID, language); err != nil { + _ = s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ + Type: discordgo.InteractionResponseChannelMessageWithSource, + Data: &discordgo.InteractionResponseData{ + Content: "⚠️ Fehler beim Speichern der Sprache.", + Flags: discordgo.MessageFlagsEphemeral, + }, + }) + return + } + + // Antwort <= 3s + _ = s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ + Type: discordgo.InteractionResponseChannelMessageWithSource, + Data: &discordgo.InteractionResponseData{ + Content: fmt.Sprintf("✅ Sprache auf '%s' gesetzt.", language), + Flags: discordgo.MessageFlagsEphemeral, + }, + }) + } } } @@ -756,7 +838,7 @@ func onGuildCreate(s *discordgo.Session, g *discordgo.GuildCreate) { commands = append(commands.([]*discordgo.ApplicationCommand), c) createdCmds.Store(g.ID, commands) - log.Printf("Command-Registrierung in %s", g.Name) + log.Printf("Registriere Command %s in Guild %s", cmd.Name, g.Name) } } @@ -809,22 +891,22 @@ var ( }, }, }, + { + Name: "setlanguage", + Description: "Setzt die Sprache für diese Guild.", + DefaultMemberPermissions: &adminPerm, + Options: []*discordgo.ApplicationCommandOption{ + { + Type: discordgo.ApplicationCommandOptionString, + Name: "language", + Description: "Die Sprache, die gesetzt werden soll (z.B. 'de', 'en').", + Required: true, + }, + }, + }, } ) -/*func registerCommandsForGuild(s *discordgo.Session, guildID string) { - appID := s.State.User.ID - cmds, err := s.ApplicationCommandBulkOverwrite(appID, guildID, slashCommands) - if err != nil { - log.Printf("BulkOverwrite %s failed: %v", guildID, err) - return - } - cfgMu.Lock() - createdCmds[guildID] = cmds - cfgMu.Unlock() - //fmt.Println(createdCmds) -}*/ - // ===== main: Multi-Guild, pro Guild registrieren ===== func main() { initDB() @@ -864,9 +946,9 @@ func main() { createdCmds.Range(func(key, value interface{}) bool { for _, c := range value.([]*discordgo.ApplicationCommand) { if delErr := s.ApplicationCommandDelete(appID, key.(string), c.ID); delErr != nil { - log.Printf("Cmd-Delete (%s/%s) fehlgeschlagen: %v", key, c.Name, delErr) + log.Printf("Cmd-Delete (%s / %s) fehlgeschlagen: %v", key, c.Name, delErr) } else { - log.Printf("Cmd-Delete (%s/%s) ok", key, c.Name) + log.Printf("Cmd-Delete (%s / %s) ok", key, c.Name) } } return true @@ -877,3 +959,59 @@ func main() { } _ = s.Close() } + +// Die Struktur für die Übersetzungen. +type TranslationsStruct struct { + Language string `json:"language"` + Messages map[string]string `json:"messages"` +} + +// Temporäre Struktur zur Deserialisierung. +type TranslationFile struct { + Translations []TranslationsStruct `json:"translations"` +} + +// Globale Variable für die Übersetzungen +var translations map[string]map[string]string + +// Funktion zur Deserialisierung und Umwandlung in map[string]map[string]string +func loadTranslations(jsonData string) (map[string]map[string]string, error) { + var translationFile TranslationFile + + // Deserialisierung des JSON in die Struktur + if err := json.Unmarshal([]byte(jsonData), &translationFile); err != nil { + return nil, err + } + + // Umwandlung der Struktur in eine Map + result := make(map[string]map[string]string) + for _, trans := range translationFile.Translations { + result[trans.Language] = trans.Messages + } + + return result, nil +} + +// Funktion zur Deserialisierung und Umwandlung in map[string]map[string]string +func loadTranslationsFromFile(filename string) (map[string]map[string]string, error) { + // Dateiinhalt lesen + fileData, err := os.ReadFile(filename) + if err != nil { + return nil, fmt.Errorf("fehler beim lesen der datei: %w", err) + } + + var translationFile TranslationFile + + // Deserialisierung des JSON in die Struktur + if err := json.Unmarshal(fileData, &translationFile); err != nil { + return nil, fmt.Errorf("fehler beim deserialisieren der json-daten: %w", err) + } + + // Umwandlung der Struktur in eine Map + result := make(map[string]map[string]string) + for _, trans := range translationFile.Translations { + result[trans.Language] = trans.Messages + } + + return result, nil +}