Files
velox-bot/internal/events/meeting_voice.go
T
2026-03-17 16:59:26 +00:00

112 lines
3.2 KiB
Go

package events
import (
"context"
"fmt"
"log"
"strconv"
"time"
"velox-bot/internal/db/services"
"github.com/bwmarrin/discordgo"
)
// HandleVoiceStateUpdate implements the "meeting lobby" behavior:
// - When a user joins the configured lobby voice channel, create a temporary voice channel
// with the same UserLimit and move the user into it.
// - When a temporary channel becomes empty, delete it.
func HandleVoiceStateUpdate(s *discordgo.Session, vs *discordgo.VoiceStateUpdate, svc *services.Services) {
if svc == nil || svc.Meeting == nil {
return
}
if vs == nil || vs.VoiceState == nil {
return
}
if vs.GuildID == "" || vs.UserID == "" {
return
}
ctx := context.Background()
guildID64, _ := strconv.ParseInt(vs.GuildID, 10, 64)
// Clean up the channel the user left (if any)
if vs.BeforeUpdate != nil && vs.BeforeUpdate.ChannelID != "" {
tryCleanupTempChannel(s, ctx, svc, vs.GuildID, guildID64, vs.BeforeUpdate.ChannelID)
}
// If not joining a channel (disconnect), nothing else to do.
if vs.ChannelID == "" {
return
}
lobbyID, ok, err := svc.Meeting.GetLobbyChannel(ctx, guildID64)
if err != nil || !ok || lobbyID == 0 {
return
}
if vs.ChannelID != strconv.FormatInt(lobbyID, 10) {
return
}
lobbyCh, err := s.Channel(vs.ChannelID)
if err != nil || lobbyCh == nil {
return
}
// Create the new channel in the same category as the lobby, with same user limit.
newName := fmt.Sprintf("%s • %s", lobbyCh.Name, time.Now().Format("15:04"))
newCh, err := s.GuildChannelCreateComplex(vs.GuildID, discordgo.GuildChannelCreateData{
Name: newName,
Type: discordgo.ChannelTypeGuildVoice,
ParentID: lobbyCh.ParentID,
UserLimit: lobbyCh.UserLimit,
Bitrate: lobbyCh.Bitrate,
})
if err != nil || newCh == nil {
log.Printf("meeting: failed creating temp channel guild=%s user=%s err=%v", vs.GuildID, vs.UserID, err)
return
}
newChID64, _ := strconv.ParseInt(newCh.ID, 10, 64)
_ = svc.Meeting.AddTempChannel(ctx, guildID64, newChID64, mustParseInt64(vs.UserID))
// Move the user into the new channel.
targetID := newCh.ID
if err := s.GuildMemberMove(vs.GuildID, vs.UserID, &targetID); err != nil {
log.Printf("meeting: failed moving member guild=%s user=%s channel=%s err=%v", vs.GuildID, vs.UserID, newCh.ID, err)
// If move fails, delete the channel to avoid junk.
_, _ = s.ChannelDelete(newCh.ID)
_ = svc.Meeting.RemoveTempChannel(ctx, guildID64, newChID64)
return
}
}
func tryCleanupTempChannel(s *discordgo.Session, ctx context.Context, svc *services.Services, guildID string, guildID64 int64, channelID string) {
chID64, err := strconv.ParseInt(channelID, 10, 64)
if err != nil || chID64 == 0 {
return
}
isTemp, err := svc.Meeting.IsTempChannel(ctx, guildID64, chID64)
if err != nil || !isTemp {
return
}
// Check if anyone is still in the channel.
g, err := s.Guild(guildID)
if err != nil || g == nil {
return
}
for _, st := range g.VoiceStates {
if st != nil && st.ChannelID == channelID {
return
}
}
if _, err := s.ChannelDelete(channelID); err != nil {
// If already deleted, still remove from DB.
log.Printf("meeting: failed deleting empty temp channel guild=%s channel=%s err=%v", guildID, channelID, err)
}
_ = svc.Meeting.RemoveTempChannel(ctx, guildID64, chID64)
}