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

How would I go about retrieving the voice channel of the author of a message? #288

Closed
goddtriffin opened this issue May 7, 2020 · 12 comments

Comments

@goddtriffin
Copy link

When I receive a message via an EvtMessageCreate, I need to know what voice channel they are in (if they are in one). This is a really easy thing to do in Discord.py by just checking the existence of a message's ctx.author.voice.channel and using it apropriately. How would I go about doing that with this library?

@mikeee
Copy link
Contributor

mikeee commented May 9, 2020

disgord/cache.go

Line 1382 in 0b361f3

func (c *Cache) GetVoiceState(guildID Snowflake, params *guildVoiceStateCacheParams) (state *VoiceState, err error) {

Looks like a bit more of a faff than the python implementation but you could configure the cache then access it with the GetVoiceState method passing it your user[/author] id.

It should return any states matching the filters you've passed.

@goddtriffin
Copy link
Author

Here's my attempt:

// 1
cache, ok := client.Cache().(*disgord.Cache)
if !ok {
	log.Println("Failed to get cache...")
	return
}

// 2
state, err := cache.GetVoiceState(m.Message.GuildID, nil)
if err != nil {
	log.Printf("Cache Voice State error: %+v\n", err)
	return
}
log.Printf("state: %+v\n", state)

Problems I ran into:

  1. Is this really the proper way to retrieve a Disgord client's cache? client.Cache() returns the disgord.Cacher, but there's no GetVoiceState method on it. This is why the cast was necessary for my attempt.
  2. I always get an item with id{. . .} was not found in cacheLink error here. One problem with cache.GetVoiceState(...) func is that the second param is of a type that is not exported from the Disgord library: disgord.guildVoiceStateCacheParams. How do I get one of these, and what is its usage?

@andersfylling
Copy link
Owner

Voice have been neglected when it comes the caching. The interaction with the cache is a bit iffy, and I'm open to rewriting it. Right now I have a lot on my plate, so I'm open to suggestions.

@goddtriffin
Copy link
Author

goddtriffin commented Jul 13, 2020

It's been a while, so I thought I would update this issue with the current strategy I have employed in order to receive the current voice channel ID of the author of a message in case it can help other people out.

There are two pieces of information that directed my implementation:

  1. the only way to receive voice state information is by listening for disgord.EvtGuildCreate and disgord.EvtVoiceStateUpdate events
  2. there is no disgord-library internal cacheing of voice states

The disgord.EvtGuildCreate event is basically triggered every time your bot connects to a guild, or a new guild starts using your bot. This is handy for retrieving everyone's initial voice states.

The disgord.EvtVoiceStateUpdate event is good for receiving updates to individuals' voice states, such as them joining a voice channel, switching from one to another, or leaving one.

First thing I needed was a custom cache for storing and handling all of this information. I personally only needed an in-memory solution without the need for a database due to my bot's scope, however I designed a consistent API interface in case my needs grew. Here is what that looked like:

Interface:

// VoiceStateCache caches Disgord voice states.
type VoiceStateCache interface {
	Handle(disgord.Session, *disgord.VoiceState) error
	AddVoiceState(*disgord.VoiceState)
	GetVoiceState(disgord.Snowflake) (int, *disgord.VoiceState)
	UpdateVoiceState(disgord.Snowflake, *disgord.VoiceState)
	DeleteVoiceState(disgord.Snowflake)
}

In-memory cache implementation:

// VoiceStateCache is a cache.
type VoiceStateCache struct {
	voiceStates []*disgord.VoiceState
	mutex       sync.RWMutex
}

// NewVoiceStateCache creates a new VoiceStateCache.
func NewVoiceStateCache() *VoiceStateCache {
	return &VoiceStateCache{
		voiceStates: []*disgord.VoiceState{},
	}
}

// Handle handles a voice state event.
func (c *VoiceStateCache) Handle(session disgord.Session, voiceState *disgord.VoiceState) error {
	// get user who triggered voice state update event
	user, err := session.GetUser(context.Background(), voiceState.UserID)
	if err != nil {
		return err
	}

	// bots don't count
	if !user.Bot {
		if voiceState.ChannelID.IsZero() {
			// no channel ID; this is a 'leave voice channel' event
			c.DeleteVoiceState(user.ID)
		} else {
			// channel ID exists; user could've freshly joined a voice channel or switched to a different one
			_, oldVoiceState := c.GetVoiceState(user.ID)
			if oldVoiceState == nil {
				// this is a 'freshly joined voice channel' event
				c.AddVoiceState(voiceState)
			} else {
				// this is a 'moved between voice channels' event
				c.UpdateVoiceState(voiceState.UserID, voiceState)
			}
		}
	}

	return nil
}

// AddVoiceState adds a Disgord voice state.
func (c *VoiceStateCache) AddVoiceState(vs *disgord.VoiceState) {
	if vs == nil {
		return
	}

	c.mutex.Lock()
	defer c.mutex.Unlock()

	c.voiceStates = append(c.voiceStates, vs)
}

// GetVoiceState gets a Disgord voice state.
func (c *VoiceStateCache) GetVoiceState(userID disgord.Snowflake) (int, *disgord.VoiceState) {
	if userID.IsZero() {
		return -1, nil
	}

	c.mutex.RLock()
	defer c.mutex.RUnlock()

	for i, vs := range c.voiceStates {
		if vs.UserID == userID {
			return i, vs
		}
	}

	return -1, nil
}

// UpdateVoiceState updates a Disgord voice state.
func (c *VoiceStateCache) UpdateVoiceState(userID disgord.Snowflake, vs *disgord.VoiceState) {
	if userID.IsZero() || vs == nil {
		return
	}

	i, _ := c.GetVoiceState(userID)
	if i == -1 {
		c.AddVoiceState(vs)
		return
	}

	c.mutex.Lock()
	defer c.mutex.Unlock()

	c.voiceStates[i] = vs
}

// DeleteVoiceState deletes a Disgord voice state.
func (c *VoiceStateCache) DeleteVoiceState(userID disgord.Snowflake) {
	if userID.IsZero() {
		return
	}

	i, _ := c.GetVoiceState(userID)
	if i == -1 {
		return
	}

	c.mutex.Lock()
	defer c.mutex.Unlock()

	c.voiceStates = append(c.voiceStates[:i], c.voiceStates[i+1:]...)
}

Now that I had somewhere to store this information, I just needed to connect it to the event handlers.

First, listen for the events:

// Guild Create (when bot joins guild)
bot.On(disgord.EvtGuildCreate, bot.guildCreate)

// Voice State Update
bot.On(disgord.EvtVoiceStateUpdate, bot.voiceStateUpdate)

Then, handle them:

func (yb *YourBot) guildCreate(session disgord.Session, evt *disgord.GuildCreate) {
	for _, vs := range evt.Guild.VoiceStates {
		err := yb.voiceStateCache.Handle(session, vs)
		if err != nil {
			yb.Logger().Error(fmt.Sprintf("voiceStateCache Handle error: %+v\n", err))
		}
	}
}

func (yb *YourBot) voiceStateUpdate(session disgord.Session, evt *disgord.VoiceStateUpdate) {
	err := yb.voiceStateCache.Handle(session, evt.VoiceState)
	if err != nil {
		yb.Logger().Error(fmt.Sprintf("voiceStateCache Handle error: %+v\n", err))
	}
}

Easy as that! Hope this helps anyone that finds themselves in the same boat.

@goddtriffin
Copy link
Author

My solution is extremely(!!) inspired by the bowot bot by @saanuregh.

These are the places you can find his equivalent methods for reference:

  • Handle(disgord.Session, *disgord.VoiceState) error:here
  • AddVoiceState(*disgord.VoiceState): here
  • GetVoiceState(disgord.Snowflake) (int, *disgord.VoiceState): here
  • UpdateVoiceState(disgord.Snowflake, *disgord.VoiceState): here
  • DeleteVoiceState(disgord.Snowflake): here

@goddtriffin
Copy link
Author

Here's an example of how to use this voice state cache API in your bot:

// getVoiceChannelID retrieves the voice channel ID of the message author, if they're in one
func (yb *YourBot) getVoiceChannelID(session disgord.Session, evt *disgord.MessageCreate) disgord.Snowflake {
	_, vs := yb.voiceStateCache.GetVoiceState(evt.Message.Author.ID)
	if vs == nil {
		yb.Logger().Info(fmt.Sprintf("%s (%s) is not in a voice channel\n", evt.Message.Author.Username, evt.Message.Author.ID))
		return 0
	}

	return vs.ChannelID
}

@andersfylling
Copy link
Owner

I'm re-writing the cache a bit now. Perhaps you want to implement voice state under the new interface?

#318

I can let you know when that would be ready for PR's.

@goddtriffin
Copy link
Author

Yeah, I'd love to implement the voice state stuff into Disgord! I'll probably need a little hand holding at first (I've never contributed to OSS before), but just let me know when.

At that point we can discuss what changes you like to see to convert my quick-fix to your new cache/interface.

@goddtriffin
Copy link
Author

I forgot to make my quick-fix cache example thread safe; I've since refactored my post above to utilize sync.RWMutex.

@andersfylling
Copy link
Owner

see #311

@goddtriffin
Copy link
Author

@andersfylling Do you still want me to add some voice state caching to your new cache system?

@andersfylling
Copy link
Owner

that would be great. I only closed this, along with a few other voice state cache related issues, to keep everything in one place. Hopefully it's easy to add voice caching now

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

No branches or pull requests

3 participants