dstate

package
v2.2.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 26, 2022 License: MIT, MIT Imports: 4 Imported by: 21

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

View Source
const AllPermissions int64 = discordgo.PermissionAll

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

func IsChannelNotFound(e error) (bool, int64)

IsChannelNotFound returns true if a ErrChannelNotFound, and also the ChannelID if it was

func IsGuildNotFound

func IsGuildNotFound(e error) (bool, int64)

IsGuildNotFound returns true if a ErrGuildNotFound, and also the GuildID if it was

func IsRoleAbove

func IsRoleAbove(a, b *discordgo.Role) bool

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

type Channels

type Channels []ChannelState

Channels are a collection of Channels

func (Channels) Len

func (r Channels) Len() int

func (Channels) Less

func (r Channels) Less(i, j int) bool

func (Channels) Swap

func (r Channels) Swap(i, j int)

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 GuildSetFromGuild(guild *discordgo.Guild) *GuildSet

func (*GuildSet) GetChannel

func (gs *GuildSet) GetChannel(id int64) *ChannelState

func (*GuildSet) GetChannelOrThread

func (gs *GuildSet) GetChannelOrThread(id int64) *ChannelState

func (*GuildSet) GetEmoji

func (gs *GuildSet) GetEmoji(id int64) *discordgo.Emoji

func (*GuildSet) GetMemberPermissions

func (gs *GuildSet) GetMemberPermissions(channelID int64, memberID int64, roles []int64) (perms int64, err error)

func (*GuildSet) GetRole

func (gs *GuildSet) GetRole(id int64) *discordgo.Role

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"`

	// Whether this guild is currently unavailable (most likely due to outage).
	// 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 LightGame

type LightGame struct {
	Name    string `json:"name"`
	URL     string `json:"url,omitempty"`
	Details string `json:"details,omitempty"`
	State   string `json:"state,omitempty"`

	Type discordgo.GameType `json:"type"`
}

type MemberFields

type MemberFields struct {
	JoinedAt discordgo.Timestamp
	Roles    []int64
	Nick     string
	Avatar   string
	Pending  bool
}

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 Roles

type Roles []discordgo.Role

func (Roles) Len

func (r Roles) Len() int

func (Roles) Less

func (r Roles) Less(i, j int) bool

func (Roles) Swap

func (r Roles) Swap(i, j int)

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

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL