main.go aktualisiert
All checks were successful
release-tag / release-image (push) Successful in 1m39s
All checks were successful
release-tag / release-image (push) Successful in 1m39s
This commit is contained in:
165
main.go
165
main.go
@@ -5,6 +5,7 @@ import (
|
|||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
|
"sync"
|
||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -13,14 +14,12 @@ import (
|
|||||||
|
|
||||||
const (
|
const (
|
||||||
// optional: Voice-Channels unter dieser Kategorie anlegen (leer lassen = keine Kategorie)
|
// optional: Voice-Channels unter dieser Kategorie anlegen (leer lassen = keine Kategorie)
|
||||||
envCategoryID = "CATEGORY_ID"
|
|
||||||
// wie oft wir prüfen, ob der Channel leer ist
|
// wie oft wir prüfen, ob der Channel leer ist
|
||||||
pollInterval = 15 * time.Second
|
pollInterval = 15 * time.Second
|
||||||
|
lobbyName = "➕ Erstelle privaten Raum"
|
||||||
|
categoryName = "Private Räume"
|
||||||
)
|
)
|
||||||
|
|
||||||
var lobbyID = os.Getenv("LOBBY_CHANNEL_ID")
|
|
||||||
var discordToken = os.Getenv("DISCORD_TOKEN")
|
|
||||||
|
|
||||||
// ===== Neu: gemeinsame Helper =====
|
// ===== Neu: gemeinsame Helper =====
|
||||||
|
|
||||||
func createPrivateVCAndMove(
|
func createPrivateVCAndMove(
|
||||||
@@ -160,18 +159,29 @@ var slashCommands = []*discordgo.ApplicationCommand{
|
|||||||
|
|
||||||
// Triggert beim Joinen in die Lobby und erstellt den VC + Move via Helper
|
// Triggert beim Joinen in die Lobby und erstellt den VC + Move via Helper
|
||||||
func onVoiceStateUpdate(s *discordgo.Session, e *discordgo.VoiceStateUpdate) {
|
func onVoiceStateUpdate(s *discordgo.Session, e *discordgo.VoiceStateUpdate) {
|
||||||
// nur Join-Events in die Lobby interessieren
|
if e.UserID == "" {
|
||||||
if e.UserID == "" || e.ChannelID != lobbyID || (e.BeforeUpdate != nil && e.BeforeUpdate.ChannelID == lobbyID) {
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
// Nur Join in die definierte Lobby
|
||||||
|
lobby := findVoiceChannelIDByName(s, e.GuildID, lobbyName)
|
||||||
|
if lobby == "" || e.ChannelID != lobby || (e.BeforeUpdate != nil && e.BeforeUpdate.ChannelID == lobby) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// Bots ignorieren
|
// Bots ignorieren
|
||||||
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
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parameter laden
|
// Kategorie per Name holen/erstellen
|
||||||
categoryID := os.Getenv(envCategoryID)
|
catID, err := findOrCreateCategoryID(s, e.GuildID, categoryName)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Kategorie-Auflösung fehlgeschlagen: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parameter
|
||||||
userLimit := 0
|
userLimit := 0
|
||||||
timeoutMin := 60
|
timeoutMin := 60
|
||||||
if v := os.Getenv("TIMEOUT_MIN"); v != "" {
|
if v := os.Getenv("TIMEOUT_MIN"); v != "" {
|
||||||
@@ -184,10 +194,10 @@ func onVoiceStateUpdate(s *discordgo.Session, e *discordgo.VoiceStateUpdate) {
|
|||||||
e.GuildID,
|
e.GuildID,
|
||||||
e.UserID,
|
e.UserID,
|
||||||
safeDisplayName(m),
|
safeDisplayName(m),
|
||||||
categoryID,
|
catID,
|
||||||
userLimit,
|
userLimit,
|
||||||
timeoutMin,
|
timeoutMin,
|
||||||
e.ChannelID, // Quell-Channel: Lobby
|
lobby,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("VC-Erstellung/Move fehlgeschlagen: %v", err)
|
log.Printf("VC-Erstellung/Move fehlgeschlagen: %v", err)
|
||||||
@@ -207,58 +217,111 @@ func safeDisplayName(m *discordgo.Member) string {
|
|||||||
return m.User.Username
|
return m.User.Username
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Holt oder erstellt eine Kategorie nach Namen und gibt die ID zurück.
|
||||||
|
func findOrCreateCategoryID(s *discordgo.Session, guildID, name string) (string, error) {
|
||||||
|
chans, err := s.GuildChannels(guildID)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
for _, ch := range chans {
|
||||||
|
if ch.Type == discordgo.ChannelTypeGuildCategory && ch.Name == name {
|
||||||
|
return ch.ID, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cat, err := s.GuildChannelCreateComplex(guildID, discordgo.GuildChannelCreateData{
|
||||||
|
Name: name,
|
||||||
|
Type: discordgo.ChannelTypeGuildCategory,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return cat.ID, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sucht die ID eines Voice-Channels nach Name. Leerer String, wenn nicht gefunden.
|
||||||
|
func findVoiceChannelIDByName(s *discordgo.Session, guildID, name string) string {
|
||||||
|
chans, err := s.GuildChannels(guildID)
|
||||||
|
if err != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
for _, ch := range chans {
|
||||||
|
if ch.Type == discordgo.ChannelTypeGuildVoice && ch.Name == name {
|
||||||
|
return ch.ID
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
createdCmds = map[string][]*discordgo.ApplicationCommand{} // guildID -> cmds
|
||||||
|
registerOnce sync.Once
|
||||||
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
token := discordToken
|
token := os.Getenv("DISCORD_TOKEN")
|
||||||
if token == "" {
|
if token == "" {
|
||||||
log.Fatal("Bitte setze DISCORD_TOKEN")
|
log.Fatal("Bitte setze DISCORD_TOKEN")
|
||||||
}
|
}
|
||||||
guildID := os.Getenv("GUILD_ID") // optional: für schnelle Command-Registrierung
|
|
||||||
categoryID := os.Getenv(envCategoryID)
|
|
||||||
|
|
||||||
s, err := discordgo.New("Bot " + token)
|
s, err := discordgo.New("Bot " + token)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Session fehlgeschlagen: %v", err)
|
log.Fatalf("Session fehlgeschlagen: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Intents für Slash + Voice-State-Events
|
// Nur was wir brauchen
|
||||||
s.Identify.Intents = discordgo.IntentsGuilds | discordgo.IntentsGuildVoiceStates
|
s.Identify.Intents = discordgo.IntentsGuilds | discordgo.IntentsGuildVoiceStates
|
||||||
s.AddHandler(onVoiceStateUpdate) // ← neu
|
|
||||||
|
|
||||||
// Handlers
|
// Handlers
|
||||||
s.AddHandler(func(_ *discordgo.Session, r *discordgo.Ready) {
|
s.AddHandler(onVoiceStateUpdate)
|
||||||
|
s.AddHandler(onInteractionCreate("")) // parameter wird nicht mehr genutzt
|
||||||
|
s.AddHandler(func(s *discordgo.Session, r *discordgo.Ready) {
|
||||||
log.Printf("Eingeloggt als %s", r.User.Username)
|
log.Printf("Eingeloggt als %s", r.User.Username)
|
||||||
})
|
|
||||||
s.AddHandler(onInteractionCreate(categoryID))
|
|
||||||
|
|
||||||
|
// Ready kann bei Reconnect mehrfach feuern → nur einmal registrieren
|
||||||
|
registerOnce.Do(func() {
|
||||||
|
appID := s.State.User.ID
|
||||||
|
for _, g := range s.State.Guilds {
|
||||||
|
log.Printf("Registriere Commands in Guild: %s (%s)", g.Name, g.ID)
|
||||||
|
for _, cmd := range slashCommands {
|
||||||
|
c, err := s.ApplicationCommandCreate(appID, g.ID, cmd)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Command-Registrierung in %s fehlgeschlagen: %v", g.Name, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
createdCmds[g.ID] = append(createdCmds[g.ID], c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
log.Printf("Lobby-Name: %q | Kategorie-Name: %q", lobbyName, categoryName)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
// Start
|
||||||
if err := s.Open(); err != nil {
|
if err := s.Open(); err != nil {
|
||||||
log.Fatalf("Gateway-Start fehlgeschlagen: %v", err)
|
log.Fatalf("Gateway-Start fehlgeschlagen: %v", err)
|
||||||
}
|
}
|
||||||
log.Println("Bot online. Ctrl+C zum Beenden.")
|
log.Println("Bot online. Ctrl+C zum Beenden.")
|
||||||
|
|
||||||
// Slash-Commands registrieren (Guild-spezifisch = sofort sichtbar)
|
// Shutdown warten
|
||||||
appID := s.State.User.ID
|
|
||||||
created := make([]*discordgo.ApplicationCommand, 0, len(slashCommands))
|
|
||||||
for _, cmd := range slashCommands {
|
|
||||||
c, err := s.ApplicationCommandCreate(appID, guildID, cmd)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("Command-Registrierung fehlgeschlagen: %v", err)
|
|
||||||
}
|
|
||||||
created = append(created, c)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Shutdown
|
|
||||||
stop := make(chan os.Signal, 1)
|
stop := make(chan os.Signal, 1)
|
||||||
signal.Notify(stop, os.Interrupt, syscall.SIGTERM)
|
signal.Notify(stop, os.Interrupt, syscall.SIGTERM)
|
||||||
<-stop
|
<-stop
|
||||||
log.Println("Fahre herunter…")
|
log.Println("Fahre herunter…")
|
||||||
for _, c := range created {
|
|
||||||
_ = s.ApplicationCommandDelete(appID, guildID, c.ID)
|
// Commands wieder entfernen (best effort)
|
||||||
|
appID := s.State.User.ID
|
||||||
|
for guildID, cmds := range createdCmds {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_ = s.Close()
|
_ = s.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Slash-Command-Variante, nutzt dieselbe Helper-Logik und moved falls der User bereits in einem VC ist
|
// Slash-Command-Variante, nutzt dieselbe Helper-Logik und moved falls der User bereits in einem VC ist
|
||||||
func onInteractionCreate(categoryID string) func(s *discordgo.Session, i *discordgo.InteractionCreate) {
|
func onInteractionCreate(_ string) func(s *discordgo.Session, i *discordgo.InteractionCreate) {
|
||||||
return func(s *discordgo.Session, i *discordgo.InteractionCreate) {
|
return func(s *discordgo.Session, i *discordgo.InteractionCreate) {
|
||||||
if i.Type != discordgo.InteractionApplicationCommand {
|
if i.Type != discordgo.InteractionApplicationCommand {
|
||||||
return
|
return
|
||||||
@@ -268,7 +331,7 @@ func onInteractionCreate(categoryID string) func(s *discordgo.Session, i *discor
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Optionen lesen
|
// Optionen lesen
|
||||||
var name string // (wird nur für die Anzeige verwendet – der Helper baut "🔒 <display>" selbst)
|
var nameOpt string
|
||||||
userLimit := 0
|
userLimit := 0
|
||||||
timeoutMin := 60
|
timeoutMin := 60
|
||||||
if v := os.Getenv("TIMEOUT_MIN"); v != "" {
|
if v := os.Getenv("TIMEOUT_MIN"); v != "" {
|
||||||
@@ -277,7 +340,7 @@ func onInteractionCreate(categoryID string) func(s *discordgo.Session, i *discor
|
|||||||
for _, o := range i.ApplicationCommandData().Options {
|
for _, o := range i.ApplicationCommandData().Options {
|
||||||
switch o.Name {
|
switch o.Name {
|
||||||
case "name":
|
case "name":
|
||||||
name = o.StringValue()
|
nameOpt = o.StringValue()
|
||||||
case "user_limit":
|
case "user_limit":
|
||||||
userLimit = int(o.IntValue())
|
userLimit = int(o.IntValue())
|
||||||
case "timeout_min":
|
case "timeout_min":
|
||||||
@@ -285,18 +348,35 @@ func onInteractionCreate(categoryID string) func(s *discordgo.Session, i *discor
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Aufrufer bestimmen
|
// Aufrufer
|
||||||
user := i.User
|
user := i.User
|
||||||
if user == nil && i.Member != nil {
|
if user == nil && i.Member != nil {
|
||||||
user = i.Member.User
|
user = i.Member.User
|
||||||
}
|
}
|
||||||
display := displayName(i)
|
if user == nil {
|
||||||
if name != "" {
|
return
|
||||||
// Falls Name explizit gesetzt wurde, nimm ihn als Display-Basis
|
|
||||||
display = name
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wenn der User bereits in einem VC ist, verschieben wir ihn automatisch
|
// Kategorie per Name holen/erstellen
|
||||||
|
catID, err := findOrCreateCategoryID(s, i.GuildID, categoryName)
|
||||||
|
if err != nil {
|
||||||
|
_ = s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
|
||||||
|
Type: discordgo.InteractionResponseChannelMessageWithSource,
|
||||||
|
Data: &discordgo.InteractionResponseData{
|
||||||
|
Content: "Konnte Kategorie nicht finden/erstellen.",
|
||||||
|
Flags: discordgo.MessageFlagsEphemeral,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Display-Name bestimmen (Option > Fallback auf displayName())
|
||||||
|
display := displayName(i)
|
||||||
|
if nameOpt != "" {
|
||||||
|
display = nameOpt
|
||||||
|
}
|
||||||
|
|
||||||
|
// Falls User bereits in einem Voice-Channel ist: automatisch verschieben
|
||||||
srcVC := findUserVoiceChannelID(s, i.GuildID, user.ID) // "" wenn nicht in VC
|
srcVC := findUserVoiceChannelID(s, i.GuildID, user.ID) // "" wenn nicht in VC
|
||||||
|
|
||||||
newChan, err := createPrivateVCAndMove(
|
newChan, err := createPrivateVCAndMove(
|
||||||
@@ -304,7 +384,7 @@ func onInteractionCreate(categoryID string) func(s *discordgo.Session, i *discor
|
|||||||
i.GuildID,
|
i.GuildID,
|
||||||
user.ID,
|
user.ID,
|
||||||
display,
|
display,
|
||||||
categoryID,
|
catID,
|
||||||
userLimit,
|
userLimit,
|
||||||
timeoutMin,
|
timeoutMin,
|
||||||
srcVC, // nur wenn nicht leer wird moved
|
srcVC, // nur wenn nicht leer wird moved
|
||||||
@@ -326,7 +406,6 @@ func onInteractionCreate(categoryID string) func(s *discordgo.Session, i *discor
|
|||||||
} else {
|
} else {
|
||||||
msg += " Ich verschiebe dich jetzt dort hinein."
|
msg += " Ich verschiebe dich jetzt dort hinein."
|
||||||
}
|
}
|
||||||
|
|
||||||
_ = s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
|
_ = s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
|
||||||
Type: discordgo.InteractionResponseChannelMessageWithSource,
|
Type: discordgo.InteractionResponseChannelMessageWithSource,
|
||||||
Data: &discordgo.InteractionResponseData{
|
Data: &discordgo.InteractionResponseData{
|
||||||
|
Reference in New Issue
Block a user