From e28aaa484dac68b623a110204acec10f62a37f06 Mon Sep 17 00:00:00 2001 From: groot Date: Mon, 11 Aug 2025 17:08:27 +0000 Subject: [PATCH] main.go aktualisiert --- main.go | 225 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 225 insertions(+) diff --git a/main.go b/main.go index 6aab22d..bc42658 100644 --- a/main.go +++ b/main.go @@ -37,6 +37,52 @@ type GuildConfig struct { Language string `json:"language"` // Neue Eigenschaft für Sprache } +type LobbyRule struct { + LobbyName string + CategoryName string + TimeoutMin int +} + +func getLobbyRules(guildID string) ([]LobbyRule, error) { + rows, err := db.Query(`SELECT lobby_name, category_name, timeout_min FROM lobby_rule WHERE guild_id = ?`, guildID) + if err != nil { + return nil, err + } + defer rows.Close() + var rules []LobbyRule + for rows.Next() { + var r LobbyRule + if err := rows.Scan(&r.LobbyName, &r.CategoryName, &r.TimeoutMin); err != nil { + return nil, err + } + rules = append(rules, r) + } + return rules, rows.Err() +} + +func upsertLobbyRule(guildID, lobby, category string, timeout int) error { + if timeout < 1 { + timeout = 1 + } + _, err := db.Exec(` + INSERT INTO lobby_rule (guild_id, lobby_name, category_name, timeout_min) + VALUES (?, ?, ?, ?) + ON CONFLICT(guild_id, lobby_name) DO UPDATE SET + category_name=excluded.category_name, + timeout_min=excluded.timeout_min + `, guildID, lobby, category, timeout) + return err +} + +func deleteLobbyRule(guildID, lobby string) (bool, error) { + res, err := db.Exec(`DELETE FROM lobby_rule WHERE guild_id=? AND lobby_name=?`, guildID, lobby) + if err != nil { + return false, err + } + n, _ := res.RowsAffected() + return n > 0, nil +} + var db *sql.DB // ===== Global variables for sync.Map ===== @@ -56,6 +102,25 @@ func getLanguage(guildID string) string { return cfg.Language } +// neue Tabelle für mehrere Lobby-Regeln pro Guild +func ensureLobbyRuleTable() { + const create = ` +CREATE TABLE IF NOT EXISTS lobby_rule ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + guild_id TEXT NOT NULL, + lobby_name TEXT NOT NULL, + category_name TEXT NOT NULL, + timeout_min INTEGER NOT NULL DEFAULT 60, + UNIQUE (guild_id, lobby_name) +); +CREATE INDEX IF NOT EXISTS idx_lobby_rule_guild ON lobby_rule(guild_id); +` + _, err := db.Exec(create) + if err != nil { + log.Fatalf("Fehler beim Erstellen lobby_rule: %v", err) + } +} + // Funktion zum Hinzufügen der 'language' Spalte, falls sie nicht existiert func addLanguageColumnIfNotExists() { // Überprüfen, ob die Tabelle 'guild_config' existiert @@ -103,6 +168,7 @@ func initDB() { // Neue Spalte für die Sprache hinzufügen, wenn sie noch nicht existiert addLanguageColumnIfNotExists() + ensureLobbyRuleTable() } @@ -410,20 +476,62 @@ func onVoiceStateUpdate(s *discordgo.Session, e *discordgo.VoiceStateUpdate) { if e.UserID == "" { return } + + // 1) Versuch: Regeln aus DB + rules, err := getLobbyRules(e.GuildID) + if err != nil { + log.Printf("getLobbyRules: %v", err) + } + if len(rules) > 0 { + // checke, ob der Join in eine der konfigurierten Lobbys erfolgte + var matched *LobbyRule + for idx := range rules { + lobbyID := findVoiceChannelIDByName(s, e.GuildID, rules[idx].LobbyName) + if lobbyID != "" && e.ChannelID == lobbyID && (e.BeforeUpdate == nil || e.BeforeUpdate.ChannelID != lobbyID) { + matched = &rules[idx] + break + } + } + if matched == nil { + return + } + + m, _ := s.GuildMember(e.GuildID, e.UserID) + if m != nil && m.User.Bot { + return + } + + catID, err := findOrCreateCategoryID(s, e.GuildID, matched.CategoryName) + if err != nil { + log.Printf("Kategorie: %v", err) + return + } + + _, err = createPrivateVCAndMove(s, e.GuildID, e.UserID, safeDisplayName(m), catID, 0, matched.TimeoutMin, e.ChannelID) + if err != nil { + log.Printf("VC/Move: %v", err) + } + return + } + + // 2) Fallback: alte Single-Config (deine bisherige Logik) cfg := getCfg(e.GuildID) lobby := findVoiceChannelIDByName(s, e.GuildID, cfg.LobbyName) if lobby == "" || e.ChannelID != lobby || (e.BeforeUpdate != nil && e.BeforeUpdate.ChannelID == lobby) { return } + m, err := s.GuildMember(e.GuildID, e.UserID) if err == nil && m.User.Bot { return } + catID, err := findOrCreateCategoryID(s, e.GuildID, cfg.CategoryName) if err != nil { log.Printf("Kategorie-Auflösung fehlgeschlagen: %v", err) return } + _, err = createPrivateVCAndMove(s, e.GuildID, e.UserID, safeDisplayName(m), catID, 0, cfg.TimeoutMin, lobby) if err != nil { log.Printf("VC-Erstellung/Move fehlgeschlagen: %v", err) @@ -824,6 +932,100 @@ func onInteractionCreate(_ string) func(s *discordgo.Session, i *discordgo.Inter }, }) + case "addlobby": + if !isGuildAdmin(s, guildID, i.User, i.Member) { + _ = s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ + Type: discordgo.InteractionResponseChannelMessageWithSource, + Data: &discordgo.InteractionResponseData{Content: "❌ Admins only.", Flags: discordgo.MessageFlagsEphemeral}, + }) + return + } + var lobby, category string + timeout := int64(envTimeoutDefault(60)) + for _, o := range data.Options { + switch o.Name { + case "lobby": + lobby = o.StringValue() + case "category": + category = o.StringValue() + case "timeout": + timeout = o.IntValue() + } + } + if lobby == "" || category == "" { /* antworten mit Fehler */ + } + if err := upsertLobbyRule(guildID, lobby, category, int(timeout)); err != nil { + _ = s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ + Type: discordgo.InteractionResponseChannelMessageWithSource, + Data: &discordgo.InteractionResponseData{Content: fmt.Sprintf("⚠️ Failed: %v", err), Flags: discordgo.MessageFlagsEphemeral}, + }) + return + } + _ = s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ + Type: discordgo.InteractionResponseChannelMessageWithSource, + Data: &discordgo.InteractionResponseData{Content: fmt.Sprintf("✅ Rule saved: lobby \"%s\" → category \"%s\" (timeout %d min).", lobby, category, timeout), Flags: discordgo.MessageFlagsEphemeral}, + }) + + case "removelobby": + if !isGuildAdmin(s, guildID, i.User, i.Member) { /* gleiche Admin-Antwort */ + return + } + var lobby string + for _, o := range data.Options { + if o.Name == "lobby" { + lobby = o.StringValue() + } + } + if lobby == "" { /* Fehlerantwort */ + return + } + removed, err := deleteLobbyRule(guildID, lobby) + if err != nil { + _ = s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ + Type: discordgo.InteractionResponseChannelMessageWithSource, + Data: &discordgo.InteractionResponseData{Content: fmt.Sprintf("⚠️ Failed: %v", err), Flags: discordgo.MessageFlagsEphemeral}, + }) + return + } + msg := "ℹ️ No rule found." + if removed { + msg = "✅ Rule removed." + } + _ = s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ + Type: discordgo.InteractionResponseChannelMessageWithSource, + Data: &discordgo.InteractionResponseData{Content: msg, Flags: discordgo.MessageFlagsEphemeral}, + }) + + case "listlobbies": + if !isGuildAdmin(s, guildID, i.User, i.Member) { /* Admin-Antwort */ + return + } + rules, err := getLobbyRules(guildID) + if err != nil { + _ = s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ + Type: discordgo.InteractionResponseChannelMessageWithSource, + Data: &discordgo.InteractionResponseData{Content: fmt.Sprintf("⚠️ Failed: %v", err), Flags: discordgo.MessageFlagsEphemeral}, + }) + return + } + if len(rules) == 0 { + _ = s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ + Type: discordgo.InteractionResponseChannelMessageWithSource, + Data: &discordgo.InteractionResponseData{Content: "ℹ️ No lobby rules configured (fallback to single-config).", Flags: discordgo.MessageFlagsEphemeral}, + }) + return + } + // kompakte Ausgabe + var b strings.Builder + b.WriteString("**Configured lobby rules:**\n") + for _, r := range rules { + fmt.Fprintf(&b, "• Lobby: `%s` → Category: `%s`, Timeout: `%d min`\n", r.LobbyName, r.CategoryName, r.TimeoutMin) + } + _ = s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ + Type: discordgo.InteractionResponseChannelMessageWithSource, + Data: &discordgo.InteractionResponseData{Content: b.String(), Flags: discordgo.MessageFlagsEphemeral}, + }) + } } } @@ -911,6 +1113,29 @@ var ( }, }, }, + { + Name: "addlobby", + Description: "Adds/updates a lobby rule (lobby → category → timeout)", + DefaultMemberPermissions: &adminPerm, + Options: []*discordgo.ApplicationCommandOption{ + {Type: discordgo.ApplicationCommandOptionString, Name: "lobby", Description: "Lobby voice channel name", Required: true}, + {Type: discordgo.ApplicationCommandOptionString, Name: "category", Description: "Category for private rooms", Required: true}, + {Type: discordgo.ApplicationCommandOptionInteger, Name: "timeout", Description: "Timeout in minutes (>=1)", Required: false, MaxValue: 480}, + }, + }, + { + Name: "removelobby", + Description: "Removes a lobby rule by lobby name", + DefaultMemberPermissions: &adminPerm, + Options: []*discordgo.ApplicationCommandOption{ + {Type: discordgo.ApplicationCommandOptionString, Name: "lobby", Description: "Lobby voice channel name", Required: true}, + }, + }, + { + Name: "listlobbies", + Description: "Lists all lobby rules for this guild", + DefaultMemberPermissions: &adminPerm, + }, } )