Updated to Multi-Lobby-Service

This commit is contained in:
2025-08-11 19:08:11 +02:00
parent ed5e279a16
commit 334ef6726c

226
main.go
View File

@@ -37,6 +37,52 @@ type GuildConfig struct {
Language string `json:"language"` // Neue Eigenschaft für Sprache 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 var db *sql.DB
// ===== Global variables for sync.Map ===== // ===== Global variables for sync.Map =====
@@ -56,6 +102,25 @@ func getLanguage(guildID string) string {
return cfg.Language 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 // Funktion zum Hinzufügen der 'language' Spalte, falls sie nicht existiert
func addLanguageColumnIfNotExists() { func addLanguageColumnIfNotExists() {
// Überprüfen, ob die Tabelle 'guild_config' existiert // Ü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 // Neue Spalte für die Sprache hinzufügen, wenn sie noch nicht existiert
addLanguageColumnIfNotExists() addLanguageColumnIfNotExists()
ensureLobbyRuleTable()
} }
@@ -410,20 +476,62 @@ func onVoiceStateUpdate(s *discordgo.Session, e *discordgo.VoiceStateUpdate) {
if e.UserID == "" { if e.UserID == "" {
return 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) cfg := getCfg(e.GuildID)
lobby := findVoiceChannelIDByName(s, e.GuildID, cfg.LobbyName) lobby := findVoiceChannelIDByName(s, e.GuildID, cfg.LobbyName)
if lobby == "" || e.ChannelID != lobby || (e.BeforeUpdate != nil && e.BeforeUpdate.ChannelID == lobby) { if lobby == "" || e.ChannelID != lobby || (e.BeforeUpdate != nil && e.BeforeUpdate.ChannelID == lobby) {
return return
} }
m, err := s.GuildMember(e.GuildID, e.UserID) m, err := s.GuildMember(e.GuildID, e.UserID)
if err == nil && m.User.Bot { if err == nil && m.User.Bot {
return return
} }
catID, err := findOrCreateCategoryID(s, e.GuildID, cfg.CategoryName) catID, err := findOrCreateCategoryID(s, e.GuildID, cfg.CategoryName)
if err != nil { if err != nil {
log.Printf("Kategorie-Auflösung fehlgeschlagen: %v", err) log.Printf("Kategorie-Auflösung fehlgeschlagen: %v", err)
return return
} }
_, err = createPrivateVCAndMove(s, e.GuildID, e.UserID, safeDisplayName(m), catID, 0, cfg.TimeoutMin, lobby) _, err = createPrivateVCAndMove(s, e.GuildID, e.UserID, safeDisplayName(m), catID, 0, cfg.TimeoutMin, lobby)
if err != nil { if err != nil {
log.Printf("VC-Erstellung/Move fehlgeschlagen: %v", err) 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,
},
} }
) )
@@ -935,7 +1160,6 @@ func main() {
loadTranslationsFromFile(GetENV("TRANSLATIONS_FILE", "./language.json")) loadTranslationsFromFile(GetENV("TRANSLATIONS_FILE", "./language.json"))
token := GetENV("DISCORD_TOKEN", "") token := GetENV("DISCORD_TOKEN", "")
token = "MTQwMzg1MTM5NDQ1MjI5MTU4NA.GVi04l.qjraLIbFdi_N49UcSUv_BqK89ihb6xXY648J7A"
if token == "" { if token == "" {
log.Fatal("Bitte setze DISCORD_TOKEN") log.Fatal("Bitte setze DISCORD_TOKEN")
} }