package main import ( "context" "encoding/json" "errors" "fmt" "github.com/disgoorg/disgo" "github.com/disgoorg/disgo/bot" "github.com/disgoorg/disgo/cache" "github.com/disgoorg/disgo/discord" "github.com/disgoorg/disgo/events" "github.com/disgoorg/disgo/gateway" "github.com/disgoorg/log" "github.com/disgoorg/snowflake/v2" "io" "math/rand" "net/http" "os" "os/signal" "syscall" ) var ( token = os.Getenv("disgo_token") registerGuildID = snowflake.GetEnv("disgo_guild_id") channelVoiceGroupID snowflake.ID channelVoiceLogID snowflake.ID channelVoicePrefix = "🔉 - " appleApi = "https://grow.rievo.page/applelist/initial.json" appleList []string ) type appleApiBody struct { Status string `json:"status"` Type string `json:"type"` Version string `json:"version"` Body []string `json:"body"` } func main() { log.SetLevel(log.LevelInfo) log.Info("starting voicechannelbot...") log.Info("disgo version: ", disgo.Version) err := loadAppleList() if err != nil { log.Fatal(err) return } // intents needed client, err := disgo.New(token, bot.WithGatewayConfigOpts( gateway.WithIntents(gateway.IntentGuildVoiceStates), ), bot.WithCacheConfigOpts( cache.WithCaches( cache.FlagChannels, cache.FlagVoiceStates, cache.FlagMembers, ), ), bot.WithEventListenerFunc(joinEvent), bot.WithEventListenerFunc(moveEvent), bot.WithEventListenerFunc(leaveEvent), ) if err != nil { log.Fatal("error while building disgo instance: ", err) return } defer client.Close(context.TODO()) if err = client.OpenGateway(context.TODO()); err != nil { log.Fatal("error while connecting to gateway: ", err) } channels, err := client.Rest().GetGuildChannels(registerGuildID) for _, channel := range channels { switch channel.Name() { case "🔊-talk": channelVoiceGroupID = channel.ID() case "📋-voice": channelVoiceLogID = channel.ID() } } if channelVoiceGroupID == 0 { log.Fatal("couldn't find needed channel") } log.Infof("voicechannelbot is now running. Press CTRL-C to exit.") s := make(chan os.Signal, 1) signal.Notify(s, syscall.SIGINT, syscall.SIGTERM, os.Interrupt) <-s } func loadAppleList() error { res, err := http.Get(appleApi) if err != nil { return err } body, err := io.ReadAll(res.Body) if err != nil { return err } var data appleApiBody err = json.Unmarshal(body, &data) if err != nil { return err } appleList = data.Body log.Debug("loaded apple list of lenght: ", len(appleList)) return nil } func sendNoMention(client bot.Client, msg, msgEdit string) { message, err := client.Rest().CreateMessage(channelVoiceLogID, discord.NewMessageCreateBuilder(). SetContent(msg).Build()) if err != nil { client.Logger().Error("unable to send channel message: ", err) return } _, err = client.Rest().UpdateMessage(channelVoiceLogID, message.ID, discord.NewMessageUpdateBuilder().ClearContent().SetContent(msgEdit).Build()) if err != nil { client.Logger().Error("unable to update channel message: ", err) return } } func joinEvent(event *events.GuildVoiceJoin) { event.Client().Logger().Debug("joinEvent") channelId := *event.VoiceState.ChannelID channel, _ := event.Client().Rest().GetChannel(channelId) channelName := channel.Name() userName := event.Member.EffectiveName() userMention := event.Member.Mention() message := fmt.Sprintf("**CHANNEL LOG:** %v joined %v", userName, channelName) messageEdit := fmt.Sprintf("**CHANNEL LOG:** %v joined %v", userMention, channelName) go sendNoMention(event.Client(), message, messageEdit) updateVoiceChannels(event.Client(), false) } func moveEvent(event *events.GuildVoiceMove) { if event.OldVoiceState.ChannelID.String() == event.VoiceState.ChannelID.String() { // no that's not a move event return } event.Client().Logger().Debug("moveEvent") channelOldId := *event.OldVoiceState.ChannelID channelOld, _ := event.Client().Rest().GetChannel(channelOldId) channelOldName := channelOld.Name() channelId := *event.VoiceState.ChannelID channel, _ := event.Client().Rest().GetChannel(channelId) channelName := channel.Name() userName := event.Member.EffectiveName() userMention := event.Member.Mention() message := fmt.Sprintf("**CHANNEL LOG:** %v moved from %v to %v", userName, channelOldName, channelName) messageEdit := fmt.Sprintf("**CHANNEL LOG:** %v moved from %v to %v", userMention, channelOldName, channelName) go sendNoMention(event.Client(), message, messageEdit) updateVoiceChannels(event.Client(), false) } func leaveEvent(event *events.GuildVoiceLeave) { event.Client().Logger().Debug("leaveEvent") if event.OldVoiceState.ChannelID == nil { event.Client().Logger().Error("OldVoiceState.ChannelID missing") return } channelOldId := *event.OldVoiceState.ChannelID channelOld, _ := event.Client().Rest().GetChannel(channelOldId) channelOldName := channelOld.Name() userName := event.Member.EffectiveName() userMention := event.Member.Mention() message := fmt.Sprintf("**CHANNEL LOG:** %v left %v", userName, channelOldName) messageEdit := fmt.Sprintf("**CHANNEL LOG:** %v left %v", userMention, channelOldName) go sendNoMention(event.Client(), message, messageEdit) updateVoiceChannels(event.Client(), true) } func getVoiceChannels(client bot.Client) (map[snowflake.ID]string, error) { allChannels, err := client.Rest().GetGuildChannels(registerGuildID) if err != nil { return nil, errors.New(fmt.Sprintf("error getting channels: %v", err.Error())) } voiceChannels := make(map[snowflake.ID]string) channelVoiceGroupIDStr := channelVoiceGroupID.String() for _, channel := range allChannels { if channel.Type() == discord.ChannelTypeGuildCategory { continue } parentIDStr := channel.ParentID().String() channelType := channel.Type() if parentIDStr == channelVoiceGroupIDStr && channelType == discord.ChannelTypeGuildVoice { voiceChannels[channel.ID()] = channel.Name() } } return voiceChannels, nil } func getName(channels map[snowflake.ID]string) (string, error) { channelsReverse := make(map[string]snowflake.ID) for id, name := range channels { channelsReverse[name] = id } appleListLen := len(appleList) for i := 0; i < appleListLen; i++ { randomIndex := rand.Intn(appleListLen) pick := appleList[randomIndex] newName := fmt.Sprintf("%v%v", channelVoicePrefix, pick) _, exists := channelsReverse[newName] if exists == false { return newName, nil } } return "", errors.New("could not find unused name") } func updateVoiceChannels(client bot.Client, deleteChannels bool) { voiceChannels, err := getVoiceChannels(client) if err != nil { client.Logger().Error("error in getVoiceChannels: ", err) } voiceChannelsWithState := make(map[snowflake.ID]string) voiceChannelsEmpty := make(map[snowflake.ID]string) for key, val := range voiceChannels { voiceChannelsEmpty[key] = val } client.Caches().VoiceStatesForEach(registerGuildID, func(state discord.VoiceState) { if state.ChannelID == nil { return } channelId := *state.ChannelID name, ok := voiceChannels[channelId] if !ok { // not channel of group return } voiceChannelsWithState[channelId] = name delete(voiceChannelsEmpty, channelId) }) if len(voiceChannels) == 2 && len(voiceChannelsWithState) == 0 { client.Logger().Debug("all channels are empty") return } if len(voiceChannelsWithState) >= len(voiceChannels) { // if there are no empty voiceChannels create one client.Logger().Debug("new channel has to be created") name, err := getName(voiceChannels) if err != nil { client.Logger().Error("no empty channels") return } _, err = client.Rest().CreateGuildChannel(registerGuildID, discord.GuildVoiceChannelCreate{ Name: name, UserLimit: 77, ParentID: channelVoiceGroupID, }) if err != nil { return } return } if len(voiceChannels)-len(voiceChannelsWithState) > 1 && deleteChannels { // if there are more than one empty voiceChannels delete all but 1 client.Logger().Debug("channel has to be deleted") client.Logger().Debug("empty channels: ", voiceChannelsEmpty) for id, s := range voiceChannelsEmpty { // get the oldest channel and delete it client.Logger().Debug("deleting: ", s) err := client.Rest().DeleteChannel(id) if err != nil { client.Logger().Error("not able to delete: ", err) return } break } updateVoiceChannels(client, true) } }