Skip to content
This repository has been archived by the owner on Jul 11, 2024. It is now read-only.

Guild.VoiceStates not kept up to date #438

Open
lrstanley opened this issue Oct 31, 2021 · 2 comments
Open

Guild.VoiceStates not kept up to date #438

lrstanley opened this issue Oct 31, 2021 · 2 comments
Assignees
Labels

Comments

@lrstanley
Copy link

lrstanley commented Oct 31, 2021

Describe the bug

Guild.VoiceStates isn't kept up to date as new VoiceStateUpdate events are received. It looks as though the initial value is kept from the GuildCreate event, and never updated.

Expected behavior

It's possible this is the intended behavior (for disgord to not track state updates). If this is the case, it would be nice to have the struct field documented to make this obvious. If it's intended, could support be added to keep it up to date?

Configuration

  • Golang version: [e.g. v1.17.2]
  • Using Go modules? yes
  • Disgord version? v0.29
  • Connected to the gateway before using REST methods? yes

Additional context

Code used to replicate the issue:

package main

import (
	"context"
	"os"

	"github.com/andersfylling/disgord"
	"github.com/sirupsen/logrus"
)

var log = &logrus.Logger{
	Out:       os.Stdout,
	Formatter: new(logrus.TextFormatter),
	Hooks:     make(logrus.LevelHooks),
	Level:     logrus.DebugLevel,
}

func main() {
	client := disgord.New(disgord.Config{
		BotToken: "TRUNCATED",
		Presence: &disgord.UpdateStatusPayload{
			Since: nil,
			Game: []*disgord.Activity{
				{Name: "voice chan curator", Type: disgord.ActivityTypeGame},
			},
			Status: disgord.StatusOnline,
			AFK:    false,
		},
		DisableCache: false,
		Logger:       log,
		RejectEvents: disgord.AllEventsExcept(
			disgord.EvtReady,
			disgord.EvtResumed,
			disgord.EvtGuildCreate,
			disgord.EvtGuildUpdate,
			disgord.EvtGuildDelete,
			disgord.EvtGuildRoleCreate,
			disgord.EvtGuildRoleUpdate,
			disgord.EvtGuildRoleDelete,
			disgord.EvtChannelCreate,
			disgord.EvtChannelUpdate,
			disgord.EvtChannelDelete,
			disgord.EvtVoiceStateUpdate,
		),
	})

	gw := client.Gateway().WithContext(context.TODO())
	gw.GuildCreate(func(s disgord.Session, h *disgord.GuildCreate) {
		log.Infof("guild create: %d", len(h.Guild.VoiceStates))
	})

	gw.VoiceStateUpdate(func(s disgord.Session, h *disgord.VoiceStateUpdate) {
		guild, err := s.Guild(h.GuildID).Get()
		if err != nil {
			panic(err)
		}

		log.Infof("voice state update: %d", len(guild.VoiceStates))
	})

	if err := gw.StayConnectedUntilInterrupted(); err != nil {
		panic(err)
	}
}

Important output:

[...]
INFO[0000] guild create: 0 <- guild that has no existing users in voice channels
INFO[0000] guild create: 1 <- guild that has 1 existing user in voice channel
INFO[0018] voice state update: 1 <- user left the voice channel, but VoiceStates is still showing the user in the channel
INFO[0027] voice state update: 1 <- rejoined
INFO[0029] voice state update: 1 <- left again
[...]
@lrstanley
Copy link
Author

lrstanley commented Oct 31, 2021

What I've done in the interim:

// NewVoiceStateTracker returns a new voice tracker.
func NewVoiceStateTracker() *voiceStateTracker {
	return &voiceStateTracker{
		db: make(map[disgord.Snowflake]map[disgord.Snowflake]*disgord.VoiceState),
	}
}

type voiceStateTracker struct {
	mu sync.RWMutex
	db map[disgord.Snowflake]map[disgord.Snowflake]*disgord.VoiceState
}

// Register should be used before the connection is established. Registers
// the necessary handlers for tracking voice state changes.
func (t *voiceStateTracker) Register(gw disgord.GatewayQueryBuilder) {
	gw.GuildCreate(func(s disgord.Session, h *disgord.GuildCreate) {
		t.process(h.Guild.ID, h.Guild.VoiceStates...)
	})

	gw.VoiceStateUpdate(func(s disgord.Session, h *disgord.VoiceStateUpdate) {
		t.process(h.GuildID, h.VoiceState)
	})

	gw.GuildDelete(func(s disgord.Session, h *disgord.GuildDelete) {
		if h.UserWasRemoved() {
			t.removeGuild(h.UnavailableGuild.ID)
		}
	})
}

func (t *voiceStateTracker) process(guildID disgord.Snowflake, states ...*disgord.VoiceState) {
	if states == nil {
		return
	}

	t.mu.Lock()
	defer t.mu.Unlock()

	for _, state := range states {
		if _, ok := t.db[guildID]; !ok {
			t.db[guildID] = make(map[disgord.Snowflake]*disgord.VoiceState)
		}

		// https://discord.com/developers/docs/topics/gateway#update-voice-state
		//   channel_id: id of the voice channel client wants to join (null if disconnecting)
		if state.ChannelID.IsZero() {
			delete(t.db[guildID], state.UserID)
			continue
		}

		t.db[guildID][state.UserID] = state
	}
}

func (t *voiceStateTracker) removeGuild(guildID disgord.Snowflake) {
	if guildID.IsZero() {
		return
	}

	t.mu.Lock()
	defer t.mu.Unlock()

	delete(t.db, guildID)
}

// States returns the full list of known voice states for a given guild.
func (t *voiceStateTracker) States(guildID disgord.Snowflake) (states []*disgord.VoiceState) {
	if guildID.IsZero() {
		return states
	}

	t.mu.RLock()
	defer t.mu.RUnlock()

	if _, ok := t.db[guildID]; !ok {
		return states
	}

	states = make([]*disgord.VoiceState, 0, len(t.db[guildID]))
	for _, state := range t.db[guildID] {
		states = append(states, state)
	}

	return states
}

// UserCount returns a map where the keys are channels that have active voice states,
// and the value is the number of users in that voice channel, for a given guild.
func (t *voiceStateTracker) UserCount(guildID disgord.Snowflake) map[disgord.Snowflake]int {
	voiceCount := map[disgord.Snowflake]int{}
	for _, state := range t.States(guildID) {
		voiceCount[state.ChannelID]++
	}

	return voiceCount
}
		voiceCount[state.ChannelID]++
	}

	return voiceCount
}

Example usage:

gw := client.Gateway().WithContext(context.TODO())

voiceStates := NewVoiceStateTracker()
voiceStates.Register(gw) // registers handlers used for tracking voice states

[...]
voicestates.States(yourGuildID) // returns voice states like Guild.VoiceStates normally would

@andersfylling
Copy link
Owner

Seems this was commented out and not even completed
image

Do you want to PR your logic into Disgord? a few tests and it would be a great addition

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Projects
None yet
Development

No branches or pull requests

2 participants