README
¶
Dstate v3
v3 of dstate is a completely redesigned from scratch state tracker that is in no way compatible with the old one.
v1 and 2 had many issues, such as:
- Not pluggable, there was 1 tracker that you were forced to use, couldn't swap implementations easily
- Hard to use
- Manual lock management
- Which functions return references and which returns copies?
- Which references are fine to deref and which are not?, which fields are immutable?
- Hard to develop, very complex, a lot references, complex structs, complex locking, and so on
v3 tries to fix all of these and improve things in general by defining a interface in which there is no manual lock management for the user. All read operations are safe from returned references and objects, but write operations on them is undefined behaviour as they can reference state/cached state and have multiple readers. Another goal of this is to enable the ability to have a remote state tracker, such as implementing a seperate gateway/worker system, as such the interface is also defined with this in mind.
The core of v3 is the interface found in interface.go and a reference implementation that is a memory state tracker can be found in inmemorytracker.
The reference tracker is a per shard tracker which will be used in production with yags until its ready for a seperated gateway/worker system, because of that it's built to be very performant with a per shard lock.
The previous versions were also built during a time where not all events had a guild id attached to them, for example messages, this meant things were a bit complicated but now every event had a guild id on it which means we no longer have to do a 2 stage locking process.
Documentation
¶
Index ¶
- Constants
- func ApplyChannelPermissions(perms int64, guildID int64, overwrites []discordgo.PermissionOverwrite, ...) int64
- func CalculateBasePermissions(guildID int64, ownerID int64, guildRoles []discordgo.Role, memberID int64, ...) (perms int64)
- func CalculatePermissions(g *GuildState, guildRoles []discordgo.Role, ...) (perms int64)
- func IsChannelNotFound(e error) (bool, int64)
- func IsGuildNotFound(e error) (bool, int64)
- func IsRoleAbove(a, b *discordgo.Role) bool
- type ChannelState
- type Channels
- type ErrChannelNotFound
- type ErrGuildNotFound
- type GuildSet
- func (gs *GuildSet) GetChannel(id int64) *ChannelState
- func (gs *GuildSet) GetChannelOrThread(id int64) *ChannelState
- func (gs *GuildSet) GetEmoji(id int64) *discordgo.Emoji
- func (gs *GuildSet) GetMemberPermissions(channelID int64, memberID int64, roles []int64) (perms int64, err error)
- func (gs *GuildSet) GetRole(id int64) *discordgo.Role
- func (gs *GuildSet) GetThread(id int64) *ChannelState
- func (gs *GuildSet) GetVoiceState(userID int64) *discordgo.VoiceState
- type GuildState
- type LightGame
- type MemberFields
- type MemberState
- type MessageState
- type MessagesQuery
- type PresenceFields
- type PresenceStatus
- type Roles
- type StateTracker
Constants ¶
const AllPermissions int64 = discordgo.PermissionAll
const ChannelPermsMask = ^(discordgo.PermissionAdministrator | discordgo.PermissionManageServer | discordgo.PermissionChangeNickname | discordgo.PermissionManageServer | discordgo.PermissionManageRoles | discordgo.PermissionKickMembers | discordgo.PermissionBanMembers)
Apply this mask to channel permissions to filter them out discord performs no server side validation so this is needed as to not run into some really weird situations
Variables ¶
This section is empty.
Functions ¶
func ApplyChannelPermissions ¶
func ApplyChannelPermissions(perms int64, guildID int64, overwrites []discordgo.PermissionOverwrite, memberID int64, roles []int64) int64
ApplyChannelPermissions applies the channel permission overwrites to the input perms
func CalculateBasePermissions ¶
func CalculateBasePermissions(guildID int64, ownerID int64, guildRoles []discordgo.Role, memberID int64, roles []int64) (perms int64)
CalculateBasePermissions calculates the guild scope permissions, excluding channel overwrites
func CalculatePermissions ¶
func CalculatePermissions(g *GuildState, guildRoles []discordgo.Role, overwrites []discordgo.PermissionOverwrite, memberID int64, roles []int64) (perms int64)
CalculatePermissions calculates the full permissions of a user
func IsChannelNotFound ¶
IsChannelNotFound returns true if a ErrChannelNotFound, and also the ChannelID if it was
func IsGuildNotFound ¶
IsGuildNotFound returns true if a ErrGuildNotFound, and also the GuildID if it was
func IsRoleAbove ¶
Types ¶
type ChannelState ¶
type ChannelState struct { ID int64 `json:"id,string"` GuildID int64 `json:"guild_id,string"` Name string `json:"name"` Topic string `json:"topic"` Type discordgo.ChannelType `json:"type"` NSFW bool `json:"nsfw"` Icon string `json:"icon"` Position int `json:"position"` Bitrate int `json:"bitrate"` UserLimit int `json:"user_limit"` ParentID int64 `json:"parent_id,string"` RateLimitPerUser int `json:"rate_limit_per_user"` OwnerID int64 `json:"owner_id,string"` ThreadMetadata *discordgo.ThreadMetadata `json:"thread_metadata"` PermissionOverwrites []discordgo.PermissionOverwrite `json:"permission_overwrites"` }
func ChannelStateFromDgo ¶
func ChannelStateFromDgo(c *discordgo.Channel) ChannelState
func (*ChannelState) IsPrivate ¶
func (c *ChannelState) IsPrivate() bool
func (*ChannelState) Mention ¶ added in v2.10.0
func (c *ChannelState) Mention() (string, error)
type ErrChannelNotFound ¶
type ErrChannelNotFound struct {
ChannelID int64
}
func (*ErrChannelNotFound) Error ¶
func (e *ErrChannelNotFound) Error() string
type ErrGuildNotFound ¶
type ErrGuildNotFound struct {
GuildID int64
}
func (*ErrGuildNotFound) Error ¶
func (e *ErrGuildNotFound) Error() string
type GuildSet ¶
type GuildSet struct { GuildState Channels []ChannelState Threads []ChannelState Roles []discordgo.Role Emojis []discordgo.Emoji VoiceStates []discordgo.VoiceState }
Relatively cheap, less frequently updated things thinking: should we keep voice states in here? those are more frequently updated but ehhh should we?
func GuildSetFromGuild ¶
func (*GuildSet) GetChannel ¶
func (gs *GuildSet) GetChannel(id int64) *ChannelState
func (*GuildSet) GetChannelOrThread ¶
func (gs *GuildSet) GetChannelOrThread(id int64) *ChannelState
func (*GuildSet) GetMemberPermissions ¶
func (*GuildSet) GetThread ¶
func (gs *GuildSet) GetThread(id int64) *ChannelState
func (*GuildSet) GetVoiceState ¶
func (gs *GuildSet) GetVoiceState(userID int64) *discordgo.VoiceState
type GuildState ¶
type GuildState struct { ID int64 `json:"id,string"` Available bool `json:"available"` MemberCount int64 `json:"member_count"` OwnerID int64 `json:"owner_id,string"` Region string `json:"region"` Name string `json:"name"` Icon string `json:"icon"` Description string `json:"description"` PreferredLocale string `json:"preferred_locale"` // The ID of the AFK voice channel. AfkChannelID int64 `json:"afk_channel_id,string"` // The hash of the guild's splash. Splash string `json:"splash"` // The timeout, in seconds, before a user is considered AFK in voice. AfkTimeout int `json:"afk_timeout"` // The verification level required for the guild. VerificationLevel discordgo.VerificationLevel `json:"verification_level"` // Whether the guild is considered large. This is // determined by a member threshold in the identify packet, // and is currently hard-coded at 250 members in the library. Large bool `json:"large"` // The default message notification setting for the guild. // 0 == all messages, 1 == mentions only. DefaultMessageNotifications int `json:"default_message_notifications"` MaxPresences int `json:"max_presences"` MaxMembers int `json:"max_members"` // This field is only present in GUILD_CREATE events and websocket // update events, and thus is only present in state-cached guilds. Unavailable bool `json:"unavailable"` // The explicit content filter level ExplicitContentFilter discordgo.ExplicitContentFilterLevel `json:"explicit_content_filter"` // The list of enabled guild features Features []string `json:"features"` // Required MFA level for the guild MfaLevel discordgo.MfaLevel `json:"mfa_level"` // Whether or not the Server Widget is enabled WidgetEnabled bool `json:"widget_enabled"` // The Channel ID for the Server Widget WidgetChannelID string `json:"widget_channel_id"` // The Channel ID to which system messages are sent (eg join and leave messages) SystemChannelID string `json:"system_channel_id"` }
func GuildStateFromDgo ¶
func GuildStateFromDgo(guild *discordgo.Guild) *GuildState
type MemberFields ¶
type MemberState ¶
type MemberState struct { // All the sparse fields are always available User discordgo.User GuildID int64 // These are not always available and all usages should be checked Member *MemberFields Presence *PresenceFields }
A fully cached member
func MemberStateFromMember ¶
func MemberStateFromMember(member *discordgo.Member) *MemberState
func MemberStateFromPresence ¶
func MemberStateFromPresence(p *discordgo.PresenceUpdate) *MemberState
func (*MemberState) DgoMember ¶
func (ms *MemberState) DgoMember() *discordgo.Member
Converts a member state into a discordgo.Member this will return nil if member is not available
type MessageState ¶
type MessageState struct { ID int64 GuildID int64 ChannelID int64 Author discordgo.User Member *discordgo.Member Content string Embeds []discordgo.MessageEmbed Mentions []discordgo.User MentionRoles []int64 Attachments []discordgo.MessageAttachment ParsedCreatedAt time.Time ParsedEditedAt time.Time Deleted bool }
func MessageStateFromDgo ¶
func MessageStateFromDgo(m *discordgo.Message) *MessageState
func (*MessageState) ContentWithMentionsReplaced ¶
func (m *MessageState) ContentWithMentionsReplaced() string
type MessagesQuery ¶
type MessagesQuery struct { Buf []*MessageState // Get messages made before this ID (message_id < before) Before int64 // Get messages made after this ID (message_id > after) After int64 Limit int IncludeDeleted bool }
type PresenceFields ¶
type PresenceFields struct { // Acitvity here Game *LightGame Status PresenceStatus }
type PresenceStatus ¶
type PresenceStatus int32
const ( StatusNotSet PresenceStatus = 0 StatusOnline PresenceStatus = 1 StatusIdle PresenceStatus = 2 StatusDoNotDisturb PresenceStatus = 3 StatusInvisible PresenceStatus = 4 StatusOffline PresenceStatus = 5 )
type StateTracker ¶
type StateTracker interface { // GetGuild returns a guild set for the provided guildID, or nil if not found GetGuild(guildID int64) *GuildSet // GetShardGuilds returns all the guild sets on the shard // this will panic if shardID is below 0 or >= total shards GetShardGuilds(shardID int64) []*GuildSet // GetMember returns a member from state // Note that MemberState.Member is nil if only presence data is present, and likewise for MemberState.Presence // // returns nil if member is not found in the guild's state // which does not mean they're not a member, simply that they're not cached GetMember(guildID int64, memberID int64) *MemberState // GetMessages returns the messages of the channel, up to limit, you may pass in a pre-allocated buffer to save allocations. // If cap(buf) is less than the needed then a new one will be created and returned // if len(buf) is greater than needed, it will be sliced to the proper length GetMessages(guildID int64, channelID int64, query *MessagesQuery) []*MessageState // Calls f on all members, return true to continue or false to stop // // This is a blocking, non-concurrent operation that returns when f has either returned false or f has been called on all members // it should be safe to modify local caller variables within f without needing any syncronization on the caller side // as syncronization is done by the implmentation to ensure f is never called concurrently // // It's up to the implementation to decide how to chunk the results, it may even just be 1 chunk // The reason it can be chunked is in the cases where state is remote // // Note that f may not be called if there were no results IterateMembers(guildID int64, f func(chunk []*MemberState) bool) }
The state system for yags You are safe to read everything returned You are NOT safe to modify anything returned, as that can cause race conditions