main.go aktualisiert
All checks were successful
release-tag / release-image (push) Successful in 1m59s

This commit is contained in:
2025-08-11 17:08:27 +00:00
parent 885a838cc5
commit e28aaa484d

225
main.go
View File

@@ -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,
},
}
)