mirror of
https://github.com/diamondburned/arikawa.git
synced 2024-11-20 05:43:21 +00:00
State: Separate Store into smaller interfaces, Cabinet API
This commit refactors the Store interface in State into smaller interfaces in package store. These interfaces are combined into one structure called a "Cabinet". The default implementation of those interfaces have been rewritten in package defaultstore, while the old no-op implementation stays with the store package. This commit also omitted several state handlers for user events, as it is unclear what they are actually structured like.
This commit is contained in:
parent
a2333996a2
commit
c6679dc52c
|
@ -148,18 +148,21 @@ type (
|
||||||
// of ReadySupplementalEvent. It has slight differences to discord.Member.
|
// of ReadySupplementalEvent. It has slight differences to discord.Member.
|
||||||
SupplementalMember struct {
|
SupplementalMember struct {
|
||||||
UserID discord.UserID `json:"user_id"`
|
UserID discord.UserID `json:"user_id"`
|
||||||
|
Nick string `json:"nick,omitempty"`
|
||||||
RoleIDs []discord.RoleID `json:"roles"`
|
RoleIDs []discord.RoleID `json:"roles"`
|
||||||
|
|
||||||
JoinedAt discord.Timestamp `json:"joined_at"`
|
GuildID discord.GuildID `json:"guild_id,omitempty"`
|
||||||
|
IsPending bool `json:"is_pending,omitempty"`
|
||||||
HoistedRole discord.RoleID `json:"hoisted_role"`
|
HoistedRole discord.RoleID `json:"hoisted_role"`
|
||||||
|
|
||||||
Mute bool `json:"mute"`
|
Mute bool `json:"mute"`
|
||||||
Deaf bool `json:"deaf"`
|
Deaf bool `json:"deaf"`
|
||||||
|
|
||||||
Nick string `json:"nick,omitempty"`
|
// Joined specifies when the user joined the guild.
|
||||||
GuildID discord.GuildID `json:"guild_id,omitempty"`
|
Joined discord.Timestamp `json:"joined_at"`
|
||||||
IsPending bool `json:"is_pending,omitempty"`
|
|
||||||
PremiumSince discord.Timestamp `json:"premium_since,omitempty"`
|
// BoostedSince specifies when the user started boosting the guild.
|
||||||
|
BoostedSince discord.Timestamp `json:"premium_since,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// FriendSourceFlags describes sources that friend requests could be sent
|
// FriendSourceFlags describes sources that friend requests could be sent
|
||||||
|
@ -243,3 +246,27 @@ type (
|
||||||
LastModified discord.UnixMsTimestamp `json:"last_modified,omitempty"`
|
LastModified discord.UnixMsTimestamp `json:"last_modified,omitempty"`
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// ConvertSupplementalMember converts a SupplementalMember to a regular Member.
|
||||||
|
func ConvertSupplementalMember(sm SupplementalMember) discord.Member {
|
||||||
|
return discord.Member{
|
||||||
|
User: discord.User{ID: sm.UserID},
|
||||||
|
Nick: sm.Nick,
|
||||||
|
RoleIDs: sm.RoleIDs,
|
||||||
|
Joined: sm.Joined,
|
||||||
|
BoostedSince: sm.BoostedSince,
|
||||||
|
Deaf: sm.Deaf,
|
||||||
|
Mute: sm.Mute,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ConvertSupplementalPresence converts a SupplementalPresence to a regular
|
||||||
|
// Presence with an empty GuildID.
|
||||||
|
func ConvertSupplementalPresence(sp SupplementalPresence) Presence {
|
||||||
|
return Presence{
|
||||||
|
User: discord.User{ID: sp.UserID},
|
||||||
|
Status: sp.Status,
|
||||||
|
Activities: sp.Activities,
|
||||||
|
ClientStatus: sp.ClientStatus,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
61
internal/moreatomic/syncmap.go
Normal file
61
internal/moreatomic/syncmap.go
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
package moreatomic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Map is a thread-safe map that is a wrapper around sync.Map with slight API
|
||||||
|
// additions.
|
||||||
|
type Map struct {
|
||||||
|
smap atomic.Value
|
||||||
|
ctor func() interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
type sentinelType struct{}
|
||||||
|
|
||||||
|
var sentinel = sentinelType{}
|
||||||
|
|
||||||
|
func NewMap(ctor func() interface{}) *Map {
|
||||||
|
smap := atomic.Value{}
|
||||||
|
smap.Store(&sync.Map{})
|
||||||
|
return &Map{smap, ctor}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset swaps the internal map out with a fresh one, dropping the old map. This
|
||||||
|
// method never errors.
|
||||||
|
func (sm *Map) Reset() error {
|
||||||
|
sm.smap.Store(&sync.Map{})
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadOrStore loads an existing value or stores a new value created from the
|
||||||
|
// given constructor then return that value.
|
||||||
|
func (sm *Map) LoadOrStore(k interface{}) (v interface{}, loaded bool) {
|
||||||
|
smap := sm.smap.Load().(*sync.Map)
|
||||||
|
|
||||||
|
v, loaded = smap.LoadOrStore(k, sentinel)
|
||||||
|
if loaded {
|
||||||
|
v = sm.ctor()
|
||||||
|
smap.Store(k, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load loads an existing value; it returns ok set to false if there is no
|
||||||
|
// value with that key.
|
||||||
|
func (sm *Map) Load(k interface{}) (lv interface{}, ok bool) {
|
||||||
|
smap := sm.smap.Load().(*sync.Map)
|
||||||
|
|
||||||
|
for {
|
||||||
|
lv, ok = smap.Load(k)
|
||||||
|
if !ok {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
|
||||||
|
if lv != sentinel {
|
||||||
|
return lv, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -19,19 +19,22 @@ func (s *State) handleReady(ev *gateway.ReadyEvent) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *State) handleGuildCreate(ev *gateway.GuildCreateEvent) {
|
func (s *State) handleGuildCreate(ev *gateway.GuildCreateEvent) {
|
||||||
|
switch {
|
||||||
// this guild was unavailable, but has come back online
|
// this guild was unavailable, but has come back online
|
||||||
if s.unavailableGuilds.Delete(ev.ID) {
|
case s.unavailableGuilds.Delete(ev.ID):
|
||||||
s.Handler.Call(&GuildAvailableEvent{
|
s.Handler.Call(&GuildAvailableEvent{
|
||||||
GuildCreateEvent: ev,
|
GuildCreateEvent: ev,
|
||||||
})
|
})
|
||||||
|
|
||||||
// the guild was already unavailable when connecting to the gateway
|
// the guild was already unavailable when connecting to the gateway
|
||||||
// we can dispatch a belated GuildReadyEvent
|
// we can dispatch a belated GuildReadyEvent
|
||||||
} else if s.unreadyGuilds.Delete(ev.ID) {
|
case s.unreadyGuilds.Delete(ev.ID):
|
||||||
s.Handler.Call(&GuildReadyEvent{
|
s.Handler.Call(&GuildReadyEvent{
|
||||||
GuildCreateEvent: ev,
|
GuildCreateEvent: ev,
|
||||||
})
|
})
|
||||||
} else { // we don't know this guild, hence we just joined it
|
|
||||||
|
// we don't know this guild, hence we just joined it
|
||||||
|
default:
|
||||||
s.Handler.Call(&GuildJoinEvent{
|
s.Handler.Call(&GuildJoinEvent{
|
||||||
GuildCreateEvent: ev,
|
GuildCreateEvent: ev,
|
||||||
})
|
})
|
||||||
|
|
144
state/state.go
144
state/state.go
|
@ -10,6 +10,8 @@ import (
|
||||||
"github.com/diamondburned/arikawa/v2/gateway"
|
"github.com/diamondburned/arikawa/v2/gateway"
|
||||||
"github.com/diamondburned/arikawa/v2/internal/moreatomic"
|
"github.com/diamondburned/arikawa/v2/internal/moreatomic"
|
||||||
"github.com/diamondburned/arikawa/v2/session"
|
"github.com/diamondburned/arikawa/v2/session"
|
||||||
|
"github.com/diamondburned/arikawa/v2/state/store"
|
||||||
|
"github.com/diamondburned/arikawa/v2/state/store/defaultstore"
|
||||||
"github.com/diamondburned/arikawa/v2/utils/handler"
|
"github.com/diamondburned/arikawa/v2/utils/handler"
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
|
@ -58,7 +60,7 @@ var (
|
||||||
// will be empty, while the Member structure expects it to be there.
|
// will be empty, while the Member structure expects it to be there.
|
||||||
type State struct {
|
type State struct {
|
||||||
*session.Session
|
*session.Session
|
||||||
Store
|
store.Cabinet
|
||||||
|
|
||||||
// *: State doesn't actually keep track of pinned messages.
|
// *: State doesn't actually keep track of pinned messages.
|
||||||
|
|
||||||
|
@ -97,7 +99,7 @@ type State struct {
|
||||||
|
|
||||||
// New creates a new state.
|
// New creates a new state.
|
||||||
func New(token string) (*State, error) {
|
func New(token string) (*State, error) {
|
||||||
return NewWithStore(token, NewDefaultStore(nil))
|
return NewWithStore(token, defaultstore.New())
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewWithIntents creates a new state with the given gateway intents. For more
|
// NewWithIntents creates a new state with the given gateway intents. For more
|
||||||
|
@ -108,24 +110,24 @@ func NewWithIntents(token string, intents ...gateway.Intents) (*State, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return NewFromSession(s, NewDefaultStore(nil))
|
return NewFromSession(s, defaultstore.New())
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewWithStore(token string, store Store) (*State, error) {
|
func NewWithStore(token string, cabinet store.Cabinet) (*State, error) {
|
||||||
s, err := session.New(token)
|
s, err := session.New(token)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return NewFromSession(s, store)
|
return NewFromSession(s, cabinet)
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewFromSession never returns an error. This API is kept for backwards
|
// NewFromSession never returns an error. This API is kept for backwards
|
||||||
// compatibility.
|
// compatibility.
|
||||||
func NewFromSession(s *session.Session, store Store) (*State, error) {
|
func NewFromSession(s *session.Session, cabinet store.Cabinet) (*State, error) {
|
||||||
state := &State{
|
state := &State{
|
||||||
Session: s,
|
Session: s,
|
||||||
Store: store,
|
Cabinet: cabinet,
|
||||||
Handler: handler.New(),
|
Handler: handler.New(),
|
||||||
StateLog: func(err error) {},
|
StateLog: func(err error) {},
|
||||||
readyMu: new(sync.Mutex),
|
readyMu: new(sync.Mutex),
|
||||||
|
@ -148,18 +150,18 @@ func (s *State) WithContext(ctx context.Context) *State {
|
||||||
return &copied
|
return &copied
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ready takes in a callback to access the Ready event in a thread-safe manner.
|
// Ready returns a copy of the Ready event. Although this function is safe to
|
||||||
// As it acquires a mutex for thread-safety, the callback shouldn't do anything
|
// call concurrently, its values should still not be changed, as certain types
|
||||||
// blocking to prevent stalling the state updates. It should also not reference
|
// like slices are not concurrent-safe.
|
||||||
// or copy the Ready instance, as that instance will not be thread-safe.
|
|
||||||
//
|
//
|
||||||
// Note that the Ready that passed in will never be nil; if Ready events are not
|
// Note that if Ready events are not received yet, then the returned event will
|
||||||
// received yet, then the pointer will point to State's zero-value Ready
|
// be a zero-value Ready instance.
|
||||||
// instance.
|
func (s *State) Ready() gateway.ReadyEvent {
|
||||||
func (s *State) Ready(fn func(*gateway.ReadyEvent)) {
|
|
||||||
s.readyMu.Lock()
|
s.readyMu.Lock()
|
||||||
fn(&s.ready)
|
r := s.ready
|
||||||
s.readyMu.Unlock()
|
s.readyMu.Unlock()
|
||||||
|
|
||||||
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
//// Helper methods
|
//// Helper methods
|
||||||
|
@ -218,18 +220,18 @@ func (s *State) MemberColor(guildID discord.GuildID, userID discord.UserID) (dis
|
||||||
|
|
||||||
var (
|
var (
|
||||||
g *discord.Guild
|
g *discord.Guild
|
||||||
gerr = ErrStoreNotFound
|
|
||||||
|
|
||||||
m *discord.Member
|
m *discord.Member
|
||||||
merr = ErrStoreNotFound
|
|
||||||
|
gerr = store.ErrNotFound
|
||||||
|
merr = store.ErrNotFound
|
||||||
)
|
)
|
||||||
|
|
||||||
if s.Gateway.HasIntents(gateway.IntentGuilds) {
|
if s.Gateway.HasIntents(gateway.IntentGuilds) {
|
||||||
g, gerr = s.Store.Guild(guildID)
|
g, gerr = s.Cabinet.Guild(guildID)
|
||||||
}
|
}
|
||||||
|
|
||||||
if s.Gateway.HasIntents(gateway.IntentGuildMembers) {
|
if s.Gateway.HasIntents(gateway.IntentGuildMembers) {
|
||||||
m, merr = s.Store.Member(guildID, userID)
|
m, merr = s.Cabinet.Member(guildID, userID)
|
||||||
}
|
}
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
|
@ -273,18 +275,18 @@ func (s *State) Permissions(
|
||||||
|
|
||||||
var (
|
var (
|
||||||
g *discord.Guild
|
g *discord.Guild
|
||||||
gerr = ErrStoreNotFound
|
|
||||||
|
|
||||||
m *discord.Member
|
m *discord.Member
|
||||||
merr = ErrStoreNotFound
|
|
||||||
|
gerr = store.ErrNotFound
|
||||||
|
merr = store.ErrNotFound
|
||||||
)
|
)
|
||||||
|
|
||||||
if s.Gateway.HasIntents(gateway.IntentGuilds) {
|
if s.Gateway.HasIntents(gateway.IntentGuilds) {
|
||||||
g, gerr = s.Store.Guild(ch.GuildID)
|
g, gerr = s.Cabinet.Guild(ch.GuildID)
|
||||||
}
|
}
|
||||||
|
|
||||||
if s.Gateway.HasIntents(gateway.IntentGuildMembers) {
|
if s.Gateway.HasIntents(gateway.IntentGuildMembers) {
|
||||||
m, merr = s.Store.Member(ch.GuildID, userID)
|
m, merr = s.Cabinet.Member(ch.GuildID, userID)
|
||||||
}
|
}
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
|
@ -317,7 +319,7 @@ func (s *State) Permissions(
|
||||||
////
|
////
|
||||||
|
|
||||||
func (s *State) Me() (*discord.User, error) {
|
func (s *State) Me() (*discord.User, error) {
|
||||||
u, err := s.Store.Me()
|
u, err := s.Cabinet.Me()
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return u, nil
|
return u, nil
|
||||||
}
|
}
|
||||||
|
@ -327,13 +329,13 @@ func (s *State) Me() (*discord.User, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return u, s.Store.MyselfSet(*u)
|
return u, s.Cabinet.MyselfSet(*u)
|
||||||
}
|
}
|
||||||
|
|
||||||
////
|
////
|
||||||
|
|
||||||
func (s *State) Channel(id discord.ChannelID) (c *discord.Channel, err error) {
|
func (s *State) Channel(id discord.ChannelID) (c *discord.Channel, err error) {
|
||||||
c, err = s.Store.Channel(id)
|
c, err = s.Cabinet.Channel(id)
|
||||||
if err == nil && s.tracksChannel(c) {
|
if err == nil && s.tracksChannel(c) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -344,7 +346,7 @@ func (s *State) Channel(id discord.ChannelID) (c *discord.Channel, err error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if s.tracksChannel(c) {
|
if s.tracksChannel(c) {
|
||||||
err = s.Store.ChannelSet(*c)
|
err = s.Cabinet.ChannelSet(*c)
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
|
@ -352,7 +354,7 @@ func (s *State) Channel(id discord.ChannelID) (c *discord.Channel, err error) {
|
||||||
|
|
||||||
func (s *State) Channels(guildID discord.GuildID) (cs []discord.Channel, err error) {
|
func (s *State) Channels(guildID discord.GuildID) (cs []discord.Channel, err error) {
|
||||||
if s.Gateway.HasIntents(gateway.IntentGuilds) {
|
if s.Gateway.HasIntents(gateway.IntentGuilds) {
|
||||||
cs, err = s.Store.Channels(guildID)
|
cs, err = s.Cabinet.Channels(guildID)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -365,7 +367,7 @@ func (s *State) Channels(guildID discord.GuildID) (cs []discord.Channel, err err
|
||||||
|
|
||||||
if s.Gateway.HasIntents(gateway.IntentGuilds) {
|
if s.Gateway.HasIntents(gateway.IntentGuilds) {
|
||||||
for _, c := range cs {
|
for _, c := range cs {
|
||||||
if err = s.Store.ChannelSet(c); err != nil {
|
if err = s.Cabinet.ChannelSet(c); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -375,7 +377,7 @@ func (s *State) Channels(guildID discord.GuildID) (cs []discord.Channel, err err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *State) CreatePrivateChannel(recipient discord.UserID) (*discord.Channel, error) {
|
func (s *State) CreatePrivateChannel(recipient discord.UserID) (*discord.Channel, error) {
|
||||||
c, err := s.Store.CreatePrivateChannel(recipient)
|
c, err := s.Cabinet.CreatePrivateChannel(recipient)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return c, nil
|
return c, nil
|
||||||
}
|
}
|
||||||
|
@ -385,13 +387,13 @@ func (s *State) CreatePrivateChannel(recipient discord.UserID) (*discord.Channel
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return c, s.Store.ChannelSet(*c)
|
return c, s.Cabinet.ChannelSet(*c)
|
||||||
}
|
}
|
||||||
|
|
||||||
// PrivateChannels gets the direct messages of the user.
|
// PrivateChannels gets the direct messages of the user.
|
||||||
// This is not supported for bots.
|
// This is not supported for bots.
|
||||||
func (s *State) PrivateChannels() ([]discord.Channel, error) {
|
func (s *State) PrivateChannels() ([]discord.Channel, error) {
|
||||||
cs, err := s.Store.PrivateChannels()
|
cs, err := s.Cabinet.PrivateChannels()
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return cs, nil
|
return cs, nil
|
||||||
}
|
}
|
||||||
|
@ -402,7 +404,7 @@ func (s *State) PrivateChannels() ([]discord.Channel, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, c := range cs {
|
for _, c := range cs {
|
||||||
if err := s.Store.ChannelSet(c); err != nil {
|
if err := s.Cabinet.ChannelSet(c); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -416,7 +418,7 @@ func (s *State) Emoji(
|
||||||
guildID discord.GuildID, emojiID discord.EmojiID) (e *discord.Emoji, err error) {
|
guildID discord.GuildID, emojiID discord.EmojiID) (e *discord.Emoji, err error) {
|
||||||
|
|
||||||
if s.Gateway.HasIntents(gateway.IntentGuildEmojis) {
|
if s.Gateway.HasIntents(gateway.IntentGuildEmojis) {
|
||||||
e, err = s.Store.Emoji(guildID, emojiID)
|
e, err = s.Cabinet.Emoji(guildID, emojiID)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -429,7 +431,7 @@ func (s *State) Emoji(
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = s.Store.EmojiSet(guildID, es); err != nil {
|
if err = s.Cabinet.EmojiSet(guildID, es); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -439,12 +441,12 @@ func (s *State) Emoji(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, ErrStoreNotFound
|
return nil, store.ErrNotFound
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *State) Emojis(guildID discord.GuildID) (es []discord.Emoji, err error) {
|
func (s *State) Emojis(guildID discord.GuildID) (es []discord.Emoji, err error) {
|
||||||
if s.Gateway.HasIntents(gateway.IntentGuildEmojis) {
|
if s.Gateway.HasIntents(gateway.IntentGuildEmojis) {
|
||||||
es, err = s.Store.Emojis(guildID)
|
es, err = s.Cabinet.Emojis(guildID)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -456,7 +458,7 @@ func (s *State) Emojis(guildID discord.GuildID) (es []discord.Emoji, err error)
|
||||||
}
|
}
|
||||||
|
|
||||||
if s.Gateway.HasIntents(gateway.IntentGuildEmojis) {
|
if s.Gateway.HasIntents(gateway.IntentGuildEmojis) {
|
||||||
err = s.Store.EmojiSet(guildID, es)
|
err = s.Cabinet.EmojiSet(guildID, es)
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
|
@ -466,7 +468,7 @@ func (s *State) Emojis(guildID discord.GuildID) (es []discord.Emoji, err error)
|
||||||
|
|
||||||
func (s *State) Guild(id discord.GuildID) (*discord.Guild, error) {
|
func (s *State) Guild(id discord.GuildID) (*discord.Guild, error) {
|
||||||
if s.Gateway.HasIntents(gateway.IntentGuilds) {
|
if s.Gateway.HasIntents(gateway.IntentGuilds) {
|
||||||
c, err := s.Store.Guild(id)
|
c, err := s.Cabinet.Guild(id)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return c, nil
|
return c, nil
|
||||||
}
|
}
|
||||||
|
@ -478,7 +480,7 @@ func (s *State) Guild(id discord.GuildID) (*discord.Guild, error) {
|
||||||
// Guilds will only fill a maximum of 100 guilds from the API.
|
// Guilds will only fill a maximum of 100 guilds from the API.
|
||||||
func (s *State) Guilds() (gs []discord.Guild, err error) {
|
func (s *State) Guilds() (gs []discord.Guild, err error) {
|
||||||
if s.Gateway.HasIntents(gateway.IntentGuilds) {
|
if s.Gateway.HasIntents(gateway.IntentGuilds) {
|
||||||
gs, err = s.Store.Guilds()
|
gs, err = s.Cabinet.Guilds()
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -491,7 +493,7 @@ func (s *State) Guilds() (gs []discord.Guild, err error) {
|
||||||
|
|
||||||
if s.Gateway.HasIntents(gateway.IntentGuilds) {
|
if s.Gateway.HasIntents(gateway.IntentGuilds) {
|
||||||
for _, g := range gs {
|
for _, g := range gs {
|
||||||
if err = s.Store.GuildSet(g); err != nil {
|
if err = s.Cabinet.GuildSet(g); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -504,7 +506,7 @@ func (s *State) Guilds() (gs []discord.Guild, err error) {
|
||||||
|
|
||||||
func (s *State) Member(guildID discord.GuildID, userID discord.UserID) (*discord.Member, error) {
|
func (s *State) Member(guildID discord.GuildID, userID discord.UserID) (*discord.Member, error) {
|
||||||
if s.Gateway.HasIntents(gateway.IntentGuildMembers) {
|
if s.Gateway.HasIntents(gateway.IntentGuildMembers) {
|
||||||
m, err := s.Store.Member(guildID, userID)
|
m, err := s.Cabinet.Member(guildID, userID)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return m, nil
|
return m, nil
|
||||||
}
|
}
|
||||||
|
@ -515,7 +517,7 @@ func (s *State) Member(guildID discord.GuildID, userID discord.UserID) (*discord
|
||||||
|
|
||||||
func (s *State) Members(guildID discord.GuildID) (ms []discord.Member, err error) {
|
func (s *State) Members(guildID discord.GuildID) (ms []discord.Member, err error) {
|
||||||
if s.Gateway.HasIntents(gateway.IntentGuildMembers) {
|
if s.Gateway.HasIntents(gateway.IntentGuildMembers) {
|
||||||
ms, err = s.Store.Members(guildID)
|
ms, err = s.Cabinet.Members(guildID)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -528,7 +530,7 @@ func (s *State) Members(guildID discord.GuildID) (ms []discord.Member, err error
|
||||||
|
|
||||||
if s.Gateway.HasIntents(gateway.IntentGuildMembers) {
|
if s.Gateway.HasIntents(gateway.IntentGuildMembers) {
|
||||||
for _, m := range ms {
|
for _, m := range ms {
|
||||||
if err = s.Store.MemberSet(guildID, m); err != nil {
|
if err = s.Cabinet.MemberSet(guildID, m); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -542,7 +544,7 @@ func (s *State) Members(guildID discord.GuildID) (ms []discord.Member, err error
|
||||||
func (s *State) Message(
|
func (s *State) Message(
|
||||||
channelID discord.ChannelID, messageID discord.MessageID) (*discord.Message, error) {
|
channelID discord.ChannelID, messageID discord.MessageID) (*discord.Message, error) {
|
||||||
|
|
||||||
m, err := s.Store.Message(channelID, messageID)
|
m, err := s.Cabinet.Message(channelID, messageID)
|
||||||
if err == nil && s.tracksMessage(m) {
|
if err == nil && s.tracksMessage(m) {
|
||||||
return m, nil
|
return m, nil
|
||||||
}
|
}
|
||||||
|
@ -551,16 +553,16 @@ func (s *State) Message(
|
||||||
wg sync.WaitGroup
|
wg sync.WaitGroup
|
||||||
|
|
||||||
c *discord.Channel
|
c *discord.Channel
|
||||||
cerr = ErrStoreNotFound
|
cerr = store.ErrNotFound
|
||||||
)
|
)
|
||||||
|
|
||||||
c, cerr = s.Store.Channel(channelID)
|
c, cerr = s.Cabinet.Channel(channelID)
|
||||||
if cerr != nil || !s.tracksChannel(c) {
|
if cerr != nil || !s.tracksChannel(c) {
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
go func() {
|
go func() {
|
||||||
c, cerr = s.Session.Channel(channelID)
|
c, cerr = s.Session.Channel(channelID)
|
||||||
if cerr == nil && s.Gateway.HasIntents(gateway.IntentGuilds) {
|
if cerr == nil && s.Gateway.HasIntents(gateway.IntentGuilds) {
|
||||||
cerr = s.Store.ChannelSet(*c)
|
cerr = s.Cabinet.ChannelSet(*c)
|
||||||
}
|
}
|
||||||
|
|
||||||
wg.Done()
|
wg.Done()
|
||||||
|
@ -582,7 +584,7 @@ func (s *State) Message(
|
||||||
m.GuildID = c.GuildID
|
m.GuildID = c.GuildID
|
||||||
|
|
||||||
if s.tracksMessage(m) {
|
if s.tracksMessage(m) {
|
||||||
err = s.Store.MessageSet(*m)
|
err = s.Cabinet.MessageSet(*m)
|
||||||
}
|
}
|
||||||
|
|
||||||
return m, err
|
return m, err
|
||||||
|
@ -594,7 +596,7 @@ func (s *State) Messages(channelID discord.ChannelID) ([]discord.Message, error)
|
||||||
// TODO: Think of a design that doesn't rely on MaxMessages().
|
// TODO: Think of a design that doesn't rely on MaxMessages().
|
||||||
var maxMsgs = s.MaxMessages()
|
var maxMsgs = s.MaxMessages()
|
||||||
|
|
||||||
ms, err := s.Store.Messages(channelID)
|
ms, err := s.Cabinet.Messages(channelID)
|
||||||
if err == nil && (len(ms) == 0 || s.tracksMessage(&ms[0])) {
|
if err == nil && (len(ms) == 0 || s.tracksMessage(&ms[0])) {
|
||||||
// If the state already has as many messages as it can, skip the API.
|
// If the state already has as many messages as it can, skip the API.
|
||||||
if maxMsgs <= len(ms) {
|
if maxMsgs <= len(ms) {
|
||||||
|
@ -635,7 +637,7 @@ func (s *State) Messages(channelID discord.ChannelID) ([]discord.Message, error)
|
||||||
// Set the guild ID, fine if it's 0 (it's already 0 anyway).
|
// Set the guild ID, fine if it's 0 (it's already 0 anyway).
|
||||||
ms[i].GuildID = guildID
|
ms[i].GuildID = guildID
|
||||||
|
|
||||||
if err := s.Store.MessageSet(ms[i]); err != nil {
|
if err := s.Cabinet.MessageSet(ms[i]); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -659,41 +661,39 @@ func (s *State) Messages(channelID discord.ChannelID) ([]discord.Message, error)
|
||||||
|
|
||||||
// Presence checks the state for user presences. If no guildID is given, it
|
// Presence checks the state for user presences. If no guildID is given, it
|
||||||
// will look for the presence in all cached guilds.
|
// will look for the presence in all cached guilds.
|
||||||
func (s *State) Presence(
|
func (s *State) Presence(gID discord.GuildID, uID discord.UserID) (*gateway.Presence, error) {
|
||||||
guildID discord.GuildID, userID discord.UserID) (*discord.Presence, error) {
|
|
||||||
|
|
||||||
if !s.Gateway.HasIntents(gateway.IntentGuildPresences) {
|
if !s.Gateway.HasIntents(gateway.IntentGuildPresences) {
|
||||||
return nil, ErrStoreNotFound
|
return nil, store.ErrNotFound
|
||||||
}
|
}
|
||||||
|
|
||||||
// If there's no guild ID, look in all guilds
|
// If there's no guild ID, look in all guilds
|
||||||
if !guildID.IsValid() {
|
if !gID.IsValid() {
|
||||||
if !s.Gateway.HasIntents(gateway.IntentGuilds) {
|
if !s.Gateway.HasIntents(gateway.IntentGuilds) {
|
||||||
return nil, ErrStoreNotFound
|
return nil, store.ErrNotFound
|
||||||
}
|
}
|
||||||
|
|
||||||
g, err := s.Store.Guilds()
|
g, err := s.Cabinet.Guilds()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, g := range g {
|
for _, g := range g {
|
||||||
if p, err := s.Store.Presence(g.ID, userID); err == nil {
|
if p, err := s.Cabinet.Presence(g.ID, uID); err == nil {
|
||||||
return p, nil
|
return p, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, ErrStoreNotFound
|
return nil, store.ErrNotFound
|
||||||
}
|
}
|
||||||
|
|
||||||
return s.Store.Presence(guildID, userID)
|
return s.Cabinet.Presence(gID, uID)
|
||||||
}
|
}
|
||||||
|
|
||||||
////
|
////
|
||||||
|
|
||||||
func (s *State) Role(guildID discord.GuildID, roleID discord.RoleID) (target *discord.Role, err error) {
|
func (s *State) Role(guildID discord.GuildID, roleID discord.RoleID) (target *discord.Role, err error) {
|
||||||
if s.Gateway.HasIntents(gateway.IntentGuilds) {
|
if s.Gateway.HasIntents(gateway.IntentGuilds) {
|
||||||
target, err = s.Store.Role(guildID, roleID)
|
target, err = s.Cabinet.Role(guildID, roleID)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -718,14 +718,14 @@ func (s *State) Role(guildID discord.GuildID, roleID discord.RoleID) (target *di
|
||||||
}
|
}
|
||||||
|
|
||||||
if target == nil {
|
if target == nil {
|
||||||
return nil, ErrStoreNotFound
|
return nil, store.ErrNotFound
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *State) Roles(guildID discord.GuildID) ([]discord.Role, error) {
|
func (s *State) Roles(guildID discord.GuildID) ([]discord.Role, error) {
|
||||||
rs, err := s.Store.Roles(guildID)
|
rs, err := s.Cabinet.Roles(guildID)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return rs, nil
|
return rs, nil
|
||||||
}
|
}
|
||||||
|
@ -749,18 +749,16 @@ func (s *State) Roles(guildID discord.GuildID) ([]discord.Role, error) {
|
||||||
func (s *State) fetchGuild(id discord.GuildID) (g *discord.Guild, err error) {
|
func (s *State) fetchGuild(id discord.GuildID) (g *discord.Guild, err error) {
|
||||||
g, err = s.Session.Guild(id)
|
g, err = s.Session.Guild(id)
|
||||||
if err == nil && s.Gateway.HasIntents(gateway.IntentGuilds) {
|
if err == nil && s.Gateway.HasIntents(gateway.IntentGuilds) {
|
||||||
err = s.Store.GuildSet(*g)
|
err = s.Cabinet.GuildSet(*g)
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *State) fetchMember(
|
func (s *State) fetchMember(gID discord.GuildID, uID discord.UserID) (m *discord.Member, err error) {
|
||||||
guildID discord.GuildID, userID discord.UserID) (m *discord.Member, err error) {
|
m, err = s.Session.Member(gID, uID)
|
||||||
|
|
||||||
m, err = s.Session.Member(guildID, userID)
|
|
||||||
if err == nil && s.Gateway.HasIntents(gateway.IntentGuildMembers) {
|
if err == nil && s.Gateway.HasIntents(gateway.IntentGuildMembers) {
|
||||||
err = s.Store.MemberSet(guildID, *m)
|
err = s.Cabinet.MemberSet(gID, *m)
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
|
@ -5,6 +5,7 @@ import (
|
||||||
|
|
||||||
"github.com/diamondburned/arikawa/v2/discord"
|
"github.com/diamondburned/arikawa/v2/discord"
|
||||||
"github.com/diamondburned/arikawa/v2/gateway"
|
"github.com/diamondburned/arikawa/v2/gateway"
|
||||||
|
"github.com/diamondburned/arikawa/v2/state/store"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (s *State) hookSession() {
|
func (s *State) hookSession() {
|
||||||
|
@ -56,57 +57,53 @@ func (s *State) onEvent(iface interface{}) {
|
||||||
s.ready = *ev
|
s.ready = *ev
|
||||||
|
|
||||||
// Reset the store before proceeding.
|
// Reset the store before proceeding.
|
||||||
if err := s.Store.Reset(); err != nil {
|
if err := s.Cabinet.Reset(); err != nil {
|
||||||
s.stateErr(err, "failed to reset state on READY")
|
s.stateErr(err, "failed to reset state on READY")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle presences
|
|
||||||
for _, p := range ev.Presences {
|
|
||||||
if err := s.Store.PresenceSet(0, p); err != nil {
|
|
||||||
s.stateErr(err, "failed to set global presence")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle guilds
|
// Handle guilds
|
||||||
for i := range ev.Guilds {
|
for i := range ev.Guilds {
|
||||||
s.batchLog(storeGuildCreate(s.Store, &ev.Guilds[i]))
|
s.batchLog(storeGuildCreate(s.Cabinet, &ev.Guilds[i]))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle private channels
|
// Handle private channels
|
||||||
for _, ch := range ev.PrivateChannels {
|
for _, ch := range ev.PrivateChannels {
|
||||||
if err := s.Store.ChannelSet(ch); err != nil {
|
if err := s.Cabinet.ChannelSet(ch); err != nil {
|
||||||
s.stateErr(err, "failed to set channel in state")
|
s.stateErr(err, "failed to set channel in state")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle user
|
// Handle user
|
||||||
if err := s.Store.MyselfSet(ev.User); err != nil {
|
if err := s.Cabinet.MyselfSet(ev.User); err != nil {
|
||||||
s.stateErr(err, "failed to set self in state")
|
s.stateErr(err, "failed to set self in state")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Release the ready mutex only after we're done with everything.
|
// Release the ready mutex only after we're done with everything.
|
||||||
s.readyMu.Unlock()
|
s.readyMu.Unlock()
|
||||||
|
|
||||||
|
case *gateway.ReadySupplementalEvent:
|
||||||
|
// TODO
|
||||||
|
|
||||||
case *gateway.GuildCreateEvent:
|
case *gateway.GuildCreateEvent:
|
||||||
s.batchLog(storeGuildCreate(s.Store, ev))
|
s.batchLog(storeGuildCreate(s.Cabinet, ev))
|
||||||
|
|
||||||
case *gateway.GuildUpdateEvent:
|
case *gateway.GuildUpdateEvent:
|
||||||
if err := s.Store.GuildSet(ev.Guild); err != nil {
|
if err := s.Cabinet.GuildSet(ev.Guild); err != nil {
|
||||||
s.stateErr(err, "failed to update guild in state")
|
s.stateErr(err, "failed to update guild in state")
|
||||||
}
|
}
|
||||||
|
|
||||||
case *gateway.GuildDeleteEvent:
|
case *gateway.GuildDeleteEvent:
|
||||||
if err := s.Store.GuildRemove(ev.ID); err != nil && !ev.Unavailable {
|
if err := s.Cabinet.GuildRemove(ev.ID); err != nil && !ev.Unavailable {
|
||||||
s.stateErr(err, "failed to delete guild in state")
|
s.stateErr(err, "failed to delete guild in state")
|
||||||
}
|
}
|
||||||
|
|
||||||
case *gateway.GuildMemberAddEvent:
|
case *gateway.GuildMemberAddEvent:
|
||||||
if err := s.Store.MemberSet(ev.GuildID, ev.Member); err != nil {
|
if err := s.Cabinet.MemberSet(ev.GuildID, ev.Member); err != nil {
|
||||||
s.stateErr(err, "failed to add a member in state")
|
s.stateErr(err, "failed to add a member in state")
|
||||||
}
|
}
|
||||||
|
|
||||||
case *gateway.GuildMemberUpdateEvent:
|
case *gateway.GuildMemberUpdateEvent:
|
||||||
m, err := s.Store.Member(ev.GuildID, ev.User.ID)
|
m, err := s.Cabinet.Member(ev.GuildID, ev.User.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// We can't do much here.
|
// We can't do much here.
|
||||||
m = &discord.Member{}
|
m = &discord.Member{}
|
||||||
|
@ -115,60 +112,60 @@ func (s *State) onEvent(iface interface{}) {
|
||||||
// Update available fields from ev into m
|
// Update available fields from ev into m
|
||||||
ev.Update(m)
|
ev.Update(m)
|
||||||
|
|
||||||
if err := s.Store.MemberSet(ev.GuildID, *m); err != nil {
|
if err := s.Cabinet.MemberSet(ev.GuildID, *m); err != nil {
|
||||||
s.stateErr(err, "failed to update a member in state")
|
s.stateErr(err, "failed to update a member in state")
|
||||||
}
|
}
|
||||||
|
|
||||||
case *gateway.GuildMemberRemoveEvent:
|
case *gateway.GuildMemberRemoveEvent:
|
||||||
if err := s.Store.MemberRemove(ev.GuildID, ev.User.ID); err != nil {
|
if err := s.Cabinet.MemberRemove(ev.GuildID, ev.User.ID); err != nil {
|
||||||
s.stateErr(err, "failed to remove a member in state")
|
s.stateErr(err, "failed to remove a member in state")
|
||||||
}
|
}
|
||||||
|
|
||||||
case *gateway.GuildMembersChunkEvent:
|
case *gateway.GuildMembersChunkEvent:
|
||||||
for _, m := range ev.Members {
|
for _, m := range ev.Members {
|
||||||
if err := s.Store.MemberSet(ev.GuildID, m); err != nil {
|
if err := s.Cabinet.MemberSet(ev.GuildID, m); err != nil {
|
||||||
s.stateErr(err, "failed to add a member from chunk in state")
|
s.stateErr(err, "failed to add a member from chunk in state")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, p := range ev.Presences {
|
for _, p := range ev.Presences {
|
||||||
if err := s.Store.PresenceSet(ev.GuildID, p); err != nil {
|
if err := s.Cabinet.PresenceSet(ev.GuildID, p); err != nil {
|
||||||
s.stateErr(err, "failed to add a presence from chunk in state")
|
s.stateErr(err, "failed to add a presence from chunk in state")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
case *gateway.GuildRoleCreateEvent:
|
case *gateway.GuildRoleCreateEvent:
|
||||||
if err := s.Store.RoleSet(ev.GuildID, ev.Role); err != nil {
|
if err := s.Cabinet.RoleSet(ev.GuildID, ev.Role); err != nil {
|
||||||
s.stateErr(err, "failed to add a role in state")
|
s.stateErr(err, "failed to add a role in state")
|
||||||
}
|
}
|
||||||
|
|
||||||
case *gateway.GuildRoleUpdateEvent:
|
case *gateway.GuildRoleUpdateEvent:
|
||||||
if err := s.Store.RoleSet(ev.GuildID, ev.Role); err != nil {
|
if err := s.Cabinet.RoleSet(ev.GuildID, ev.Role); err != nil {
|
||||||
s.stateErr(err, "failed to update a role in state")
|
s.stateErr(err, "failed to update a role in state")
|
||||||
}
|
}
|
||||||
|
|
||||||
case *gateway.GuildRoleDeleteEvent:
|
case *gateway.GuildRoleDeleteEvent:
|
||||||
if err := s.Store.RoleRemove(ev.GuildID, ev.RoleID); err != nil {
|
if err := s.Cabinet.RoleRemove(ev.GuildID, ev.RoleID); err != nil {
|
||||||
s.stateErr(err, "failed to remove a role in state")
|
s.stateErr(err, "failed to remove a role in state")
|
||||||
}
|
}
|
||||||
|
|
||||||
case *gateway.GuildEmojisUpdateEvent:
|
case *gateway.GuildEmojisUpdateEvent:
|
||||||
if err := s.Store.EmojiSet(ev.GuildID, ev.Emojis); err != nil {
|
if err := s.Cabinet.EmojiSet(ev.GuildID, ev.Emojis); err != nil {
|
||||||
s.stateErr(err, "failed to update emojis in state")
|
s.stateErr(err, "failed to update emojis in state")
|
||||||
}
|
}
|
||||||
|
|
||||||
case *gateway.ChannelCreateEvent:
|
case *gateway.ChannelCreateEvent:
|
||||||
if err := s.Store.ChannelSet(ev.Channel); err != nil {
|
if err := s.Cabinet.ChannelSet(ev.Channel); err != nil {
|
||||||
s.stateErr(err, "failed to create a channel in state")
|
s.stateErr(err, "failed to create a channel in state")
|
||||||
}
|
}
|
||||||
|
|
||||||
case *gateway.ChannelUpdateEvent:
|
case *gateway.ChannelUpdateEvent:
|
||||||
if err := s.Store.ChannelSet(ev.Channel); err != nil {
|
if err := s.Cabinet.ChannelSet(ev.Channel); err != nil {
|
||||||
s.stateErr(err, "failed to update a channel in state")
|
s.stateErr(err, "failed to update a channel in state")
|
||||||
}
|
}
|
||||||
|
|
||||||
case *gateway.ChannelDeleteEvent:
|
case *gateway.ChannelDeleteEvent:
|
||||||
if err := s.Store.ChannelRemove(ev.Channel); err != nil {
|
if err := s.Cabinet.ChannelRemove(ev.Channel); err != nil {
|
||||||
s.stateErr(err, "failed to remove a channel in state")
|
s.stateErr(err, "failed to remove a channel in state")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -176,23 +173,23 @@ func (s *State) onEvent(iface interface{}) {
|
||||||
// not tracked.
|
// not tracked.
|
||||||
|
|
||||||
case *gateway.MessageCreateEvent:
|
case *gateway.MessageCreateEvent:
|
||||||
if err := s.Store.MessageSet(ev.Message); err != nil {
|
if err := s.Cabinet.MessageSet(ev.Message); err != nil {
|
||||||
s.stateErr(err, "failed to add a message in state")
|
s.stateErr(err, "failed to add a message in state")
|
||||||
}
|
}
|
||||||
|
|
||||||
case *gateway.MessageUpdateEvent:
|
case *gateway.MessageUpdateEvent:
|
||||||
if err := s.Store.MessageSet(ev.Message); err != nil {
|
if err := s.Cabinet.MessageSet(ev.Message); err != nil {
|
||||||
s.stateErr(err, "failed to update a message in state")
|
s.stateErr(err, "failed to update a message in state")
|
||||||
}
|
}
|
||||||
|
|
||||||
case *gateway.MessageDeleteEvent:
|
case *gateway.MessageDeleteEvent:
|
||||||
if err := s.Store.MessageRemove(ev.ChannelID, ev.ID); err != nil {
|
if err := s.Cabinet.MessageRemove(ev.ChannelID, ev.ID); err != nil {
|
||||||
s.stateErr(err, "failed to delete a message in state")
|
s.stateErr(err, "failed to delete a message in state")
|
||||||
}
|
}
|
||||||
|
|
||||||
case *gateway.MessageDeleteBulkEvent:
|
case *gateway.MessageDeleteBulkEvent:
|
||||||
for _, id := range ev.IDs {
|
for _, id := range ev.IDs {
|
||||||
if err := s.Store.MessageRemove(ev.ChannelID, id); err != nil {
|
if err := s.Cabinet.MessageRemove(ev.ChannelID, id); err != nil {
|
||||||
s.stateErr(err, "failed to delete bulk messages in state")
|
s.stateErr(err, "failed to delete bulk messages in state")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -203,7 +200,7 @@ func (s *State) onEvent(iface interface{}) {
|
||||||
m.Reactions[i].Count++
|
m.Reactions[i].Count++
|
||||||
} else {
|
} else {
|
||||||
var me bool
|
var me bool
|
||||||
if u, _ := s.Store.Me(); u != nil {
|
if u, _ := s.Cabinet.Me(); u != nil {
|
||||||
me = ev.UserID == u.ID
|
me = ev.UserID == u.ID
|
||||||
}
|
}
|
||||||
m.Reactions = append(m.Reactions, discord.Reaction{
|
m.Reactions = append(m.Reactions, discord.Reaction{
|
||||||
|
@ -231,7 +228,7 @@ func (s *State) onEvent(iface interface{}) {
|
||||||
m.Reactions = append(m.Reactions[:i], m.Reactions[i+1:]...)
|
m.Reactions = append(m.Reactions[:i], m.Reactions[i+1:]...)
|
||||||
|
|
||||||
case r.Me: // If reaction removal is the user's
|
case r.Me: // If reaction removal is the user's
|
||||||
u, err := s.Store.Me()
|
u, err := s.Cabinet.Me()
|
||||||
if err == nil && ev.UserID == u.ID {
|
if err == nil && ev.UserID == u.ID {
|
||||||
r.Me = false
|
r.Me = false
|
||||||
}
|
}
|
||||||
|
@ -257,51 +254,44 @@ func (s *State) onEvent(iface interface{}) {
|
||||||
})
|
})
|
||||||
|
|
||||||
case *gateway.PresenceUpdateEvent:
|
case *gateway.PresenceUpdateEvent:
|
||||||
if err := s.Store.PresenceSet(ev.GuildID, ev.Presence); err != nil {
|
if err := s.Cabinet.PresenceSet(ev.GuildID, ev.Presence); err != nil {
|
||||||
s.stateErr(err, "failed to update presence in state")
|
s.stateErr(err, "failed to update presence in state")
|
||||||
}
|
}
|
||||||
|
|
||||||
case *gateway.PresencesReplaceEvent:
|
case *gateway.PresencesReplaceEvent:
|
||||||
for _, p := range *ev {
|
for _, p := range *ev {
|
||||||
if err := s.Store.PresenceSet(p.GuildID, p); err != nil {
|
if err := s.Cabinet.PresenceSet(p.GuildID, p.Presence); err != nil {
|
||||||
s.stateErr(err, "failed to update presence in state")
|
s.stateErr(err, "failed to update presence in state")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
case *gateway.SessionsReplaceEvent:
|
case *gateway.SessionsReplaceEvent:
|
||||||
|
// TODO
|
||||||
|
|
||||||
case *gateway.UserGuildSettingsUpdateEvent:
|
case *gateway.UserGuildSettingsUpdateEvent:
|
||||||
s.readyMu.Lock()
|
// TODO
|
||||||
for i, ugs := range s.ready.UserGuildSettings {
|
|
||||||
if ugs.GuildID == ev.GuildID {
|
|
||||||
s.ready.UserGuildSettings[i] = ev.UserGuildSettings
|
|
||||||
}
|
|
||||||
}
|
|
||||||
s.readyMu.Unlock()
|
|
||||||
|
|
||||||
case *gateway.UserSettingsUpdateEvent:
|
case *gateway.UserSettingsUpdateEvent:
|
||||||
s.readyMu.Lock()
|
s.readyMu.Lock()
|
||||||
s.ready.Settings = &ev.UserSettings
|
s.ready.UserSettings = &ev.UserSettings
|
||||||
s.readyMu.Unlock()
|
s.readyMu.Unlock()
|
||||||
|
|
||||||
case *gateway.UserNoteUpdateEvent:
|
case *gateway.UserNoteUpdateEvent:
|
||||||
s.readyMu.Lock()
|
// TODO
|
||||||
s.ready.Notes[ev.ID] = ev.Note
|
|
||||||
s.readyMu.Unlock()
|
|
||||||
|
|
||||||
case *gateway.UserUpdateEvent:
|
case *gateway.UserUpdateEvent:
|
||||||
if err := s.Store.MyselfSet(ev.User); err != nil {
|
if err := s.Cabinet.MyselfSet(ev.User); err != nil {
|
||||||
s.stateErr(err, "failed to update myself from USER_UPDATE")
|
s.stateErr(err, "failed to update myself from USER_UPDATE")
|
||||||
}
|
}
|
||||||
|
|
||||||
case *gateway.VoiceStateUpdateEvent:
|
case *gateway.VoiceStateUpdateEvent:
|
||||||
vs := &ev.VoiceState
|
vs := &ev.VoiceState
|
||||||
if vs.ChannelID == 0 {
|
if vs.ChannelID == 0 {
|
||||||
if err := s.Store.VoiceStateRemove(vs.GuildID, vs.UserID); err != nil {
|
if err := s.Cabinet.VoiceStateRemove(vs.GuildID, vs.UserID); err != nil {
|
||||||
s.stateErr(err, "failed to remove voice state from state")
|
s.stateErr(err, "failed to remove voice state from state")
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if err := s.Store.VoiceStateSet(vs.GuildID, *vs); err != nil {
|
if err := s.Cabinet.VoiceStateSet(vs.GuildID, *vs); err != nil {
|
||||||
s.stateErr(err, "failed to update voice state in state")
|
s.stateErr(err, "failed to update voice state in state")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -320,14 +310,14 @@ func (s *State) batchLog(errors []error) {
|
||||||
// Helper functions
|
// Helper functions
|
||||||
|
|
||||||
func (s *State) editMessage(ch discord.ChannelID, msg discord.MessageID, fn func(m *discord.Message) bool) {
|
func (s *State) editMessage(ch discord.ChannelID, msg discord.MessageID, fn func(m *discord.Message) bool) {
|
||||||
m, err := s.Store.Message(ch, msg)
|
m, err := s.Cabinet.Message(ch, msg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if !fn(m) {
|
if !fn(m) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err := s.Store.MessageSet(*m); err != nil {
|
if err := s.Cabinet.MessageSet(*m); err != nil {
|
||||||
s.stateErr(err, "failed to save message in reaction add")
|
s.stateErr(err, "failed to save message in reaction add")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -341,27 +331,27 @@ func findReaction(rs []discord.Reaction, emoji discord.Emoji) int {
|
||||||
return -1
|
return -1
|
||||||
}
|
}
|
||||||
|
|
||||||
func storeGuildCreate(store Store, guild *gateway.GuildCreateEvent) []error {
|
func storeGuildCreate(cab store.Cabinet, guild *gateway.GuildCreateEvent) []error {
|
||||||
if guild.Unavailable {
|
if guild.Unavailable {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
stack, errs := newErrorStack()
|
stack, errs := newErrorStack()
|
||||||
|
|
||||||
if err := store.GuildSet(guild.Guild); err != nil {
|
if err := cab.GuildSet(guild.Guild); err != nil {
|
||||||
errs(err, "failed to set guild in Ready")
|
errs(err, "failed to set guild in Ready")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle guild emojis
|
// Handle guild emojis
|
||||||
if guild.Emojis != nil {
|
if guild.Emojis != nil {
|
||||||
if err := store.EmojiSet(guild.ID, guild.Emojis); err != nil {
|
if err := cab.EmojiSet(guild.ID, guild.Emojis); err != nil {
|
||||||
errs(err, "failed to set guild emojis")
|
errs(err, "failed to set guild emojis")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle guild member
|
// Handle guild member
|
||||||
for _, m := range guild.Members {
|
for _, m := range guild.Members {
|
||||||
if err := store.MemberSet(guild.ID, m); err != nil {
|
if err := cab.MemberSet(guild.ID, m); err != nil {
|
||||||
errs(err, "failed to set guild member in Ready")
|
errs(err, "failed to set guild member in Ready")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -371,21 +361,21 @@ func storeGuildCreate(store Store, guild *gateway.GuildCreateEvent) []error {
|
||||||
// I HATE Discord.
|
// I HATE Discord.
|
||||||
ch.GuildID = guild.ID
|
ch.GuildID = guild.ID
|
||||||
|
|
||||||
if err := store.ChannelSet(ch); err != nil {
|
if err := cab.ChannelSet(ch); err != nil {
|
||||||
errs(err, "failed to set guild channel in Ready")
|
errs(err, "failed to set guild channel in Ready")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle guild presences
|
// Handle guild presences
|
||||||
for _, p := range guild.Presences {
|
for _, p := range guild.Presences {
|
||||||
if err := store.PresenceSet(guild.ID, p); err != nil {
|
if err := cab.PresenceSet(guild.ID, p); err != nil {
|
||||||
errs(err, "failed to set guild presence in Ready")
|
errs(err, "failed to set guild presence in Ready")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle guild voice states
|
// Handle guild voice states
|
||||||
for _, v := range guild.VoiceStates {
|
for _, v := range guild.VoiceStates {
|
||||||
if err := store.VoiceStateSet(guild.ID, v); err != nil {
|
if err := cab.VoiceStateSet(guild.ID, v); err != nil {
|
||||||
errs(err, "failed to set guild voice state in Ready")
|
errs(err, "failed to set guild voice state in Ready")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
134
state/store.go
134
state/store.go
|
@ -1,134 +0,0 @@
|
||||||
package state
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
|
|
||||||
"github.com/diamondburned/arikawa/v2/discord"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Store is the state storage. It should handle mutex itself, and it should only
|
|
||||||
// concern itself with the local state.
|
|
||||||
type Store interface {
|
|
||||||
StoreGetter
|
|
||||||
StoreModifier
|
|
||||||
StoreResetter
|
|
||||||
}
|
|
||||||
|
|
||||||
// All methods in StoreGetter will be wrapped by the State. If the State can't
|
|
||||||
// find anything in the storage, it will call the API itself and automatically
|
|
||||||
// add what's missing into the storage.
|
|
||||||
//
|
|
||||||
// Methods that return with a slice should pay attention to race conditions that
|
|
||||||
// would mutate the underlying slice (and as a result the returned slice as
|
|
||||||
// well). The best way to avoid this is to copy the whole slice, like
|
|
||||||
// DefaultStore does.
|
|
||||||
//
|
|
||||||
// These methods should not care about returning slices in order, unless
|
|
||||||
// explicitly stated against.
|
|
||||||
type StoreGetter interface {
|
|
||||||
Me() (*discord.User, error)
|
|
||||||
|
|
||||||
// Channel should check for both DM and guild channels.
|
|
||||||
Channel(id discord.ChannelID) (*discord.Channel, error)
|
|
||||||
Channels(guildID discord.GuildID) ([]discord.Channel, error)
|
|
||||||
|
|
||||||
// same API as (*api.Client)
|
|
||||||
CreatePrivateChannel(recipient discord.UserID) (*discord.Channel, error)
|
|
||||||
PrivateChannels() ([]discord.Channel, error)
|
|
||||||
|
|
||||||
Emoji(guildID discord.GuildID, emojiID discord.EmojiID) (*discord.Emoji, error)
|
|
||||||
Emojis(guildID discord.GuildID) ([]discord.Emoji, error)
|
|
||||||
|
|
||||||
Guild(id discord.GuildID) (*discord.Guild, error)
|
|
||||||
Guilds() ([]discord.Guild, error)
|
|
||||||
|
|
||||||
Member(guildID discord.GuildID, userID discord.UserID) (*discord.Member, error)
|
|
||||||
Members(guildID discord.GuildID) ([]discord.Member, error)
|
|
||||||
|
|
||||||
Message(channelID discord.ChannelID, messageID discord.MessageID) (*discord.Message, error)
|
|
||||||
// Messages should return messages ordered from latest to earliest.
|
|
||||||
Messages(channelID discord.ChannelID) ([]discord.Message, error)
|
|
||||||
MaxMessages() int // used to know if the state is filled or not.
|
|
||||||
|
|
||||||
// These don't get fetched from the API, it's Gateway only.
|
|
||||||
Presence(guildID discord.GuildID, userID discord.UserID) (*discord.Presence, error)
|
|
||||||
Presences(guildID discord.GuildID) ([]discord.Presence, error)
|
|
||||||
|
|
||||||
Role(guildID discord.GuildID, roleID discord.RoleID) (*discord.Role, error)
|
|
||||||
Roles(guildID discord.GuildID) ([]discord.Role, error)
|
|
||||||
|
|
||||||
VoiceState(guildID discord.GuildID, userID discord.UserID) (*discord.VoiceState, error)
|
|
||||||
VoiceStates(guildID discord.GuildID) ([]discord.VoiceState, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
type StoreModifier interface {
|
|
||||||
MyselfSet(me discord.User) error
|
|
||||||
|
|
||||||
// ChannelSet should switch on Type to know if it's a private channel or
|
|
||||||
// not.
|
|
||||||
ChannelSet(discord.Channel) error
|
|
||||||
ChannelRemove(discord.Channel) error
|
|
||||||
|
|
||||||
// EmojiSet should delete all old emojis before setting new ones.
|
|
||||||
EmojiSet(guildID discord.GuildID, emojis []discord.Emoji) error
|
|
||||||
|
|
||||||
GuildSet(discord.Guild) error
|
|
||||||
GuildRemove(id discord.GuildID) error
|
|
||||||
|
|
||||||
MemberSet(guildID discord.GuildID, member discord.Member) error
|
|
||||||
MemberRemove(guildID discord.GuildID, userID discord.UserID) error
|
|
||||||
|
|
||||||
// MessageSet should prepend messages into the slice, the latest being in
|
|
||||||
// front.
|
|
||||||
MessageSet(discord.Message) error
|
|
||||||
MessageRemove(channelID discord.ChannelID, messageID discord.MessageID) error
|
|
||||||
|
|
||||||
PresenceSet(guildID discord.GuildID, presence discord.Presence) error
|
|
||||||
PresenceRemove(guildID discord.GuildID, userID discord.UserID) error
|
|
||||||
|
|
||||||
RoleSet(guildID discord.GuildID, role discord.Role) error
|
|
||||||
RoleRemove(guildID discord.GuildID, roleID discord.RoleID) error
|
|
||||||
|
|
||||||
VoiceStateSet(guildID discord.GuildID, voiceState discord.VoiceState) error
|
|
||||||
VoiceStateRemove(guildID discord.GuildID, userID discord.UserID) error
|
|
||||||
}
|
|
||||||
|
|
||||||
// StoreResetter is used by the state to reset the store on every Ready event.
|
|
||||||
type StoreResetter interface {
|
|
||||||
// Reset resets the store to a new valid instance.
|
|
||||||
Reset() error
|
|
||||||
}
|
|
||||||
|
|
||||||
// ErrStoreNotFound is an error that a store can use to return when something
|
|
||||||
// isn't in the storage. There is no strict restrictions on what uses this (the
|
|
||||||
// default one does, though), so be advised.
|
|
||||||
var ErrStoreNotFound = errors.New("item not found in store")
|
|
||||||
|
|
||||||
// DiffMessage fills non-empty fields from src to dst.
|
|
||||||
func DiffMessage(src discord.Message, dst *discord.Message) {
|
|
||||||
// Thanks, Discord.
|
|
||||||
if src.Content != "" {
|
|
||||||
dst.Content = src.Content
|
|
||||||
}
|
|
||||||
if src.EditedTimestamp.IsValid() {
|
|
||||||
dst.EditedTimestamp = src.EditedTimestamp
|
|
||||||
}
|
|
||||||
if src.Mentions != nil {
|
|
||||||
dst.Mentions = src.Mentions
|
|
||||||
}
|
|
||||||
if src.Embeds != nil {
|
|
||||||
dst.Embeds = src.Embeds
|
|
||||||
}
|
|
||||||
if src.Attachments != nil {
|
|
||||||
dst.Attachments = src.Attachments
|
|
||||||
}
|
|
||||||
if src.Timestamp.IsValid() {
|
|
||||||
dst.Timestamp = src.Timestamp
|
|
||||||
}
|
|
||||||
if src.Author.ID.IsValid() {
|
|
||||||
dst.Author = src.Author
|
|
||||||
}
|
|
||||||
if src.Reactions != nil {
|
|
||||||
dst.Reactions = src.Reactions
|
|
||||||
}
|
|
||||||
}
|
|
179
state/store/defaultstore/channel.go
Normal file
179
state/store/defaultstore/channel.go
Normal file
|
@ -0,0 +1,179 @@
|
||||||
|
package defaultstore
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/diamondburned/arikawa/v2/discord"
|
||||||
|
"github.com/diamondburned/arikawa/v2/state/store"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Channel struct {
|
||||||
|
mut sync.RWMutex
|
||||||
|
|
||||||
|
// Channel references must be protected under the same mutex.
|
||||||
|
|
||||||
|
privates map[discord.UserID]*discord.Channel
|
||||||
|
channels map[discord.ChannelID]*discord.Channel
|
||||||
|
guildChs map[discord.GuildID][]*discord.Channel
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ store.ChannelStore = (*Channel)(nil)
|
||||||
|
|
||||||
|
func NewChannel() *Channel {
|
||||||
|
return &Channel{
|
||||||
|
privates: map[discord.UserID]*discord.Channel{},
|
||||||
|
channels: map[discord.ChannelID]*discord.Channel{},
|
||||||
|
guildChs: map[discord.GuildID][]*discord.Channel{},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Channel) Reset() error {
|
||||||
|
s.mut.Lock()
|
||||||
|
defer s.mut.Unlock()
|
||||||
|
|
||||||
|
s.privates = map[discord.UserID]*discord.Channel{}
|
||||||
|
s.channels = map[discord.ChannelID]*discord.Channel{}
|
||||||
|
s.guildChs = map[discord.GuildID][]*discord.Channel{}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Channel) Channel(id discord.ChannelID) (*discord.Channel, error) {
|
||||||
|
s.mut.RLock()
|
||||||
|
defer s.mut.RUnlock()
|
||||||
|
|
||||||
|
ch, ok := s.channels[id]
|
||||||
|
if !ok {
|
||||||
|
return nil, store.ErrNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
cpy := *ch
|
||||||
|
return &cpy, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Channel) CreatePrivateChannel(recipient discord.UserID) (*discord.Channel, error) {
|
||||||
|
s.mut.RLock()
|
||||||
|
defer s.mut.RUnlock()
|
||||||
|
|
||||||
|
ch, ok := s.privates[recipient]
|
||||||
|
if !ok {
|
||||||
|
return nil, store.ErrNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
cpy := *ch
|
||||||
|
return &cpy, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Channels returns a list of Guild channels randomly ordered.
|
||||||
|
func (s *Channel) Channels(guildID discord.GuildID) ([]discord.Channel, error) {
|
||||||
|
s.mut.RLock()
|
||||||
|
defer s.mut.RUnlock()
|
||||||
|
|
||||||
|
chRefs, ok := s.guildChs[guildID]
|
||||||
|
if !ok {
|
||||||
|
return nil, store.ErrNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reading chRefs is also covered by the global mutex.
|
||||||
|
|
||||||
|
var channels = make([]discord.Channel, len(chRefs))
|
||||||
|
for i, chRef := range chRefs {
|
||||||
|
channels[i] = *chRef
|
||||||
|
}
|
||||||
|
|
||||||
|
return channels, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// PrivateChannels returns a list of Direct Message channels randomly ordered.
|
||||||
|
func (s *Channel) PrivateChannels() ([]discord.Channel, error) {
|
||||||
|
s.mut.RLock()
|
||||||
|
defer s.mut.RUnlock()
|
||||||
|
|
||||||
|
if len(s.privates) == 0 {
|
||||||
|
return nil, store.ErrNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
var channels = make([]discord.Channel, 0, len(s.privates))
|
||||||
|
for _, ch := range s.privates {
|
||||||
|
channels = append(channels, *ch)
|
||||||
|
}
|
||||||
|
|
||||||
|
return channels, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ChannelSet sets the Direct Message or Guild channl into the state. If the
|
||||||
|
// channel doesn't have 1 (one) DMRecipients, then it must have a valid GuildID,
|
||||||
|
// otherwise an error will be returned.
|
||||||
|
func (s *Channel) ChannelSet(channel discord.Channel) error {
|
||||||
|
s.mut.Lock()
|
||||||
|
defer s.mut.Unlock()
|
||||||
|
|
||||||
|
// Update the reference if we can.
|
||||||
|
if ch, ok := s.channels[channel.ID]; ok {
|
||||||
|
*ch = channel
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(channel.DMRecipients) == 1 {
|
||||||
|
s.privates[channel.DMRecipients[0].ID] = &channel
|
||||||
|
s.channels[channel.ID] = &channel
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Invalid channel case, as we need the GuildID to search for this channel.
|
||||||
|
if !channel.GuildID.IsValid() {
|
||||||
|
return errors.New("invalid guildID for guild channel")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Always ensure that if the channel is in the slice, then it will be in the
|
||||||
|
// map.
|
||||||
|
|
||||||
|
s.channels[channel.ID] = &channel
|
||||||
|
|
||||||
|
channels, _ := s.guildChs[channel.GuildID]
|
||||||
|
channels = append(channels, &channel)
|
||||||
|
s.guildChs[channel.GuildID] = channels
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Channel) ChannelRemove(channel discord.Channel) error {
|
||||||
|
s.mut.Lock()
|
||||||
|
defer s.mut.Unlock()
|
||||||
|
|
||||||
|
delete(s.channels, channel.ID)
|
||||||
|
|
||||||
|
if len(channel.DMRecipients) == 1 {
|
||||||
|
delete(s.privates, channel.DMRecipients[0].ID)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
channels, ok := s.guildChs[channel.GuildID]
|
||||||
|
if !ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, ch := range channels {
|
||||||
|
if ch.ID != channel.ID {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fast unordered delete. Not sure if there's a benefit in doing
|
||||||
|
// this over using a map, but I guess the memory usage is less and
|
||||||
|
// there's no copying.
|
||||||
|
|
||||||
|
// Move the last channel to the current channel, set the last
|
||||||
|
// channel there to a nil value to unreference its children, then
|
||||||
|
// slice the last channel off.
|
||||||
|
channels[i] = channels[len(channels)-1]
|
||||||
|
channels[len(channels)-1] = nil
|
||||||
|
channels = channels[:len(channels)-1]
|
||||||
|
|
||||||
|
s.guildChs[channel.GuildID] = channels
|
||||||
|
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
21
state/store/defaultstore/defaultstore.go
Normal file
21
state/store/defaultstore/defaultstore.go
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
// Package defaultstore provides thread-safe store implementations that store
|
||||||
|
// state values in memory.
|
||||||
|
package defaultstore
|
||||||
|
|
||||||
|
import "github.com/diamondburned/arikawa/v2/state/store"
|
||||||
|
|
||||||
|
// New creates a new cabinet instance of defaultstore. For Message, it creates a
|
||||||
|
// Message store with a limit of 100 messages.
|
||||||
|
func New() store.Cabinet {
|
||||||
|
return store.Cabinet{
|
||||||
|
MeStore: NewMe(),
|
||||||
|
ChannelStore: NewChannel(),
|
||||||
|
EmojiStore: NewEmoji(),
|
||||||
|
GuildStore: NewGuild(),
|
||||||
|
MemberStore: NewMember(),
|
||||||
|
MessageStore: NewMessage(100),
|
||||||
|
PresenceStore: NewPresence(),
|
||||||
|
RoleStore: NewRole(),
|
||||||
|
VoiceStateStore: NewVoiceState(),
|
||||||
|
}
|
||||||
|
}
|
84
state/store/defaultstore/emoji.go
Normal file
84
state/store/defaultstore/emoji.go
Normal file
|
@ -0,0 +1,84 @@
|
||||||
|
package defaultstore
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/diamondburned/arikawa/v2/discord"
|
||||||
|
"github.com/diamondburned/arikawa/v2/internal/moreatomic"
|
||||||
|
"github.com/diamondburned/arikawa/v2/state/store"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Emoji struct {
|
||||||
|
guilds moreatomic.Map
|
||||||
|
}
|
||||||
|
|
||||||
|
type emojis struct {
|
||||||
|
mut sync.Mutex
|
||||||
|
emojis []discord.Emoji
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ store.EmojiStore = (*Emoji)(nil)
|
||||||
|
|
||||||
|
func NewEmoji() *Emoji {
|
||||||
|
return &Emoji{
|
||||||
|
guilds: *moreatomic.NewMap(func() interface{} {
|
||||||
|
return &emojis{
|
||||||
|
emojis: []discord.Emoji{},
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Emoji) Reset() error {
|
||||||
|
s.guilds.Reset()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Emoji) Emoji(guildID discord.GuildID, emojiID discord.EmojiID) (*discord.Emoji, error) {
|
||||||
|
iv, ok := s.guilds.Load(guildID)
|
||||||
|
if !ok {
|
||||||
|
return nil, store.ErrNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
es := iv.(*emojis)
|
||||||
|
|
||||||
|
es.mut.Lock()
|
||||||
|
defer es.mut.Unlock()
|
||||||
|
|
||||||
|
for _, emoji := range es.emojis {
|
||||||
|
if emoji.ID == emojiID {
|
||||||
|
// Emoji is an implicit copy made by range, so we could do this
|
||||||
|
// safely.
|
||||||
|
return &emoji, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, store.ErrNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Emoji) Emojis(guildID discord.GuildID) ([]discord.Emoji, error) {
|
||||||
|
iv, ok := s.guilds.Load(guildID)
|
||||||
|
if !ok {
|
||||||
|
return nil, store.ErrNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
es := iv.(*emojis)
|
||||||
|
|
||||||
|
es.mut.Lock()
|
||||||
|
defer es.mut.Unlock()
|
||||||
|
|
||||||
|
// We're never modifying the slice internals ourselves, so this is fine.
|
||||||
|
return es.emojis, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Emoji) EmojiSet(guildID discord.GuildID, allEmojis []discord.Emoji) error {
|
||||||
|
iv, _ := s.guilds.LoadOrStore(guildID)
|
||||||
|
|
||||||
|
es := iv.(*emojis)
|
||||||
|
|
||||||
|
es.mut.Lock()
|
||||||
|
es.emojis = allEmojis
|
||||||
|
es.mut.Unlock()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
73
state/store/defaultstore/guild.go
Normal file
73
state/store/defaultstore/guild.go
Normal file
|
@ -0,0 +1,73 @@
|
||||||
|
package defaultstore
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/diamondburned/arikawa/v2/discord"
|
||||||
|
"github.com/diamondburned/arikawa/v2/state/store"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Guild struct {
|
||||||
|
mut sync.RWMutex
|
||||||
|
guilds map[discord.GuildID]discord.Guild
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ store.GuildStore = (*Guild)(nil)
|
||||||
|
|
||||||
|
func NewGuild() *Guild {
|
||||||
|
return &Guild{
|
||||||
|
guilds: map[discord.GuildID]discord.Guild{},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Guild) Reset() error {
|
||||||
|
s.mut.Lock()
|
||||||
|
defer s.mut.Unlock()
|
||||||
|
|
||||||
|
s.guilds = map[discord.GuildID]discord.Guild{}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Guild) Guild(id discord.GuildID) (*discord.Guild, error) {
|
||||||
|
s.mut.RLock()
|
||||||
|
defer s.mut.RUnlock()
|
||||||
|
|
||||||
|
ch, ok := s.guilds[id]
|
||||||
|
if !ok {
|
||||||
|
return nil, store.ErrNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
// implicit copy
|
||||||
|
return &ch, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Guild) Guilds() ([]discord.Guild, error) {
|
||||||
|
s.mut.RLock()
|
||||||
|
defer s.mut.RUnlock()
|
||||||
|
|
||||||
|
if len(s.guilds) == 0 {
|
||||||
|
return nil, store.ErrNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
var gs = make([]discord.Guild, 0, len(s.guilds))
|
||||||
|
for _, g := range s.guilds {
|
||||||
|
gs = append(gs, g)
|
||||||
|
}
|
||||||
|
|
||||||
|
return gs, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Guild) GuildSet(guild discord.Guild) error {
|
||||||
|
s.mut.Lock()
|
||||||
|
s.guilds[guild.ID] = guild
|
||||||
|
s.mut.Unlock()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Guild) GuildRemove(id discord.GuildID) error {
|
||||||
|
s.mut.Lock()
|
||||||
|
delete(s.guilds, id)
|
||||||
|
s.mut.Unlock()
|
||||||
|
return nil
|
||||||
|
}
|
47
state/store/defaultstore/me.go
Normal file
47
state/store/defaultstore/me.go
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
package defaultstore
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/diamondburned/arikawa/v2/discord"
|
||||||
|
"github.com/diamondburned/arikawa/v2/state/store"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Me struct {
|
||||||
|
mut sync.RWMutex
|
||||||
|
self discord.User
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ store.MeStore = (*Me)(nil)
|
||||||
|
|
||||||
|
func NewMe() *Me {
|
||||||
|
return &Me{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Me) Reset() error {
|
||||||
|
m.mut.Lock()
|
||||||
|
m.self = discord.User{}
|
||||||
|
m.mut.Unlock()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Me) Me() (*discord.User, error) {
|
||||||
|
m.mut.RLock()
|
||||||
|
self := m.self
|
||||||
|
m.mut.RUnlock()
|
||||||
|
|
||||||
|
if !self.ID.IsValid() {
|
||||||
|
return nil, store.ErrNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
return &self, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Me) MyselfSet(me discord.User) error {
|
||||||
|
m.mut.Lock()
|
||||||
|
m.self = me
|
||||||
|
m.mut.Unlock()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
98
state/store/defaultstore/member.go
Normal file
98
state/store/defaultstore/member.go
Normal file
|
@ -0,0 +1,98 @@
|
||||||
|
package defaultstore
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/diamondburned/arikawa/v2/discord"
|
||||||
|
"github.com/diamondburned/arikawa/v2/internal/moreatomic"
|
||||||
|
"github.com/diamondburned/arikawa/v2/state/store"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Member struct {
|
||||||
|
guilds moreatomic.Map // discord.GuildID -> *guildMembers
|
||||||
|
}
|
||||||
|
|
||||||
|
type guildMembers struct {
|
||||||
|
mut sync.Mutex
|
||||||
|
members map[discord.UserID]discord.Member
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ store.MemberStore = (*Member)(nil)
|
||||||
|
|
||||||
|
func NewMember() *Member {
|
||||||
|
return &Member{
|
||||||
|
guilds: *moreatomic.NewMap(func() interface{} {
|
||||||
|
return &guildMembers{
|
||||||
|
members: make(map[discord.UserID]discord.Member, 1),
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Member) Reset() error {
|
||||||
|
return s.guilds.Reset()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Member) Member(guildID discord.GuildID, userID discord.UserID) (*discord.Member, error) {
|
||||||
|
iv, ok := s.guilds.Load(guildID)
|
||||||
|
if !ok {
|
||||||
|
return nil, store.ErrNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
gm := iv.(*guildMembers)
|
||||||
|
|
||||||
|
gm.mut.Lock()
|
||||||
|
defer gm.mut.Unlock()
|
||||||
|
|
||||||
|
m, ok := gm.members[userID]
|
||||||
|
if ok {
|
||||||
|
return &m, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, store.ErrNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Member) Members(guildID discord.GuildID) ([]discord.Member, error) {
|
||||||
|
iv, ok := s.guilds.Load(guildID)
|
||||||
|
if !ok {
|
||||||
|
return nil, store.ErrNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
gm := iv.(*guildMembers)
|
||||||
|
|
||||||
|
gm.mut.Lock()
|
||||||
|
defer gm.mut.Unlock()
|
||||||
|
|
||||||
|
var members = make([]discord.Member, 0, len(gm.members))
|
||||||
|
for _, m := range gm.members {
|
||||||
|
members = append(members, m)
|
||||||
|
}
|
||||||
|
|
||||||
|
return members, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Member) MemberSet(guildID discord.GuildID, member discord.Member) error {
|
||||||
|
iv, _ := s.guilds.LoadOrStore(guildID)
|
||||||
|
gm := iv.(*guildMembers)
|
||||||
|
|
||||||
|
gm.mut.Lock()
|
||||||
|
gm.members[member.User.ID] = member
|
||||||
|
gm.mut.Unlock()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Member) MemberRemove(guildID discord.GuildID, userID discord.UserID) error {
|
||||||
|
iv, ok := s.guilds.Load(guildID)
|
||||||
|
if !ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
gm := iv.(*guildMembers)
|
||||||
|
|
||||||
|
gm.mut.Lock()
|
||||||
|
delete(gm.members, userID)
|
||||||
|
gm.mut.Unlock()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
162
state/store/defaultstore/message.go
Normal file
162
state/store/defaultstore/message.go
Normal file
|
@ -0,0 +1,162 @@
|
||||||
|
package defaultstore
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/diamondburned/arikawa/v2/discord"
|
||||||
|
"github.com/diamondburned/arikawa/v2/internal/moreatomic"
|
||||||
|
"github.com/diamondburned/arikawa/v2/state/store"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Message struct {
|
||||||
|
channels moreatomic.Map
|
||||||
|
maxMsgs int
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ store.MessageStore = (*Message)(nil)
|
||||||
|
|
||||||
|
type messages struct {
|
||||||
|
mut sync.Mutex
|
||||||
|
messages []discord.Message
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewMessage(maxMsgs int) *Message {
|
||||||
|
return &Message{
|
||||||
|
channels: *moreatomic.NewMap(func() interface{} {
|
||||||
|
return &messages{
|
||||||
|
messages: []discord.Message{}, // never use a nil slice
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Message) Reset() error {
|
||||||
|
return s.channels.Reset()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Message) Message(chID discord.ChannelID, mID discord.MessageID) (*discord.Message, error) {
|
||||||
|
iv, ok := s.channels.Load(chID)
|
||||||
|
if !ok {
|
||||||
|
return nil, store.ErrNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
msgs := iv.(*messages)
|
||||||
|
|
||||||
|
msgs.mut.Lock()
|
||||||
|
defer msgs.mut.Unlock()
|
||||||
|
|
||||||
|
for _, m := range msgs.messages {
|
||||||
|
if m.ID == mID {
|
||||||
|
return &m, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, store.ErrNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Message) Messages(channelID discord.ChannelID) ([]discord.Message, error) {
|
||||||
|
iv, ok := s.channels.Load(channelID)
|
||||||
|
if !ok {
|
||||||
|
return nil, store.ErrNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
msgs := iv.(*messages)
|
||||||
|
|
||||||
|
msgs.mut.Lock()
|
||||||
|
defer msgs.mut.Unlock()
|
||||||
|
|
||||||
|
return append([]discord.Message(nil), msgs.messages...), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Message) MaxMessages() int {
|
||||||
|
return s.maxMsgs
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Message) MessageSet(message discord.Message) error {
|
||||||
|
iv, _ := s.channels.LoadOrStore(message.ChannelID)
|
||||||
|
|
||||||
|
msgs := iv.(*messages)
|
||||||
|
|
||||||
|
msgs.mut.Lock()
|
||||||
|
defer msgs.mut.Unlock()
|
||||||
|
|
||||||
|
// Check if we already have the message.
|
||||||
|
for i, m := range msgs.messages {
|
||||||
|
if m.ID == message.ID {
|
||||||
|
DiffMessage(message, &m)
|
||||||
|
msgs.messages[i] = m
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Order: latest to earliest, similar to the API.
|
||||||
|
|
||||||
|
var end = len(msgs.messages)
|
||||||
|
if max := s.MaxMessages(); end >= max {
|
||||||
|
// If the end (length) is larger than the maximum amount, then cap it.
|
||||||
|
end = max
|
||||||
|
} else {
|
||||||
|
// Else, append an empty message to the end.
|
||||||
|
msgs.messages = append(msgs.messages, discord.Message{})
|
||||||
|
// Increment to update the length.
|
||||||
|
end++
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy hack to prepend. This copies the 0th-(end-1)th entries to
|
||||||
|
// 1st-endth.
|
||||||
|
copy(msgs.messages[1:end], msgs.messages[0:end-1])
|
||||||
|
// Then, set the 0th entry.
|
||||||
|
msgs.messages[0] = message
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DiffMessage fills non-empty fields from src to dst.
|
||||||
|
func DiffMessage(src discord.Message, dst *discord.Message) {
|
||||||
|
// Thanks, Discord.
|
||||||
|
if src.Content != "" {
|
||||||
|
dst.Content = src.Content
|
||||||
|
}
|
||||||
|
if src.EditedTimestamp.IsValid() {
|
||||||
|
dst.EditedTimestamp = src.EditedTimestamp
|
||||||
|
}
|
||||||
|
if src.Mentions != nil {
|
||||||
|
dst.Mentions = src.Mentions
|
||||||
|
}
|
||||||
|
if src.Embeds != nil {
|
||||||
|
dst.Embeds = src.Embeds
|
||||||
|
}
|
||||||
|
if src.Attachments != nil {
|
||||||
|
dst.Attachments = src.Attachments
|
||||||
|
}
|
||||||
|
if src.Timestamp.IsValid() {
|
||||||
|
dst.Timestamp = src.Timestamp
|
||||||
|
}
|
||||||
|
if src.Author.ID.IsValid() {
|
||||||
|
dst.Author = src.Author
|
||||||
|
}
|
||||||
|
if src.Reactions != nil {
|
||||||
|
dst.Reactions = src.Reactions
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Message) MessageRemove(channelID discord.ChannelID, messageID discord.MessageID) error {
|
||||||
|
iv, ok := s.channels.Load(channelID)
|
||||||
|
if !ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
msgs := iv.(*messages)
|
||||||
|
|
||||||
|
msgs.mut.Lock()
|
||||||
|
defer msgs.mut.Unlock()
|
||||||
|
|
||||||
|
for i, m := range msgs.messages {
|
||||||
|
if m.ID == messageID {
|
||||||
|
msgs.messages = append(msgs.messages[:i], msgs.messages[i+1:]...)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
106
state/store/defaultstore/presence.go
Normal file
106
state/store/defaultstore/presence.go
Normal file
|
@ -0,0 +1,106 @@
|
||||||
|
package defaultstore
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/diamondburned/arikawa/v2/discord"
|
||||||
|
"github.com/diamondburned/arikawa/v2/gateway"
|
||||||
|
"github.com/diamondburned/arikawa/v2/internal/moreatomic"
|
||||||
|
"github.com/diamondburned/arikawa/v2/state/store"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Presence struct {
|
||||||
|
guilds moreatomic.Map
|
||||||
|
}
|
||||||
|
|
||||||
|
type presences struct {
|
||||||
|
mut sync.Mutex
|
||||||
|
presences map[discord.UserID]gateway.Presence
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ store.PresenceStore = (*Presence)(nil)
|
||||||
|
|
||||||
|
func NewPresence() *Presence {
|
||||||
|
return &Presence{
|
||||||
|
guilds: *moreatomic.NewMap(func() interface{} {
|
||||||
|
return &presences{
|
||||||
|
presences: make(map[discord.UserID]gateway.Presence, 1),
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Presence) Reset() error {
|
||||||
|
return s.guilds.Reset()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Presence) Presence(gID discord.GuildID, uID discord.UserID) (*gateway.Presence, error) {
|
||||||
|
iv, ok := s.guilds.Load(gID)
|
||||||
|
if !ok {
|
||||||
|
return nil, store.ErrNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
ps := iv.(*presences)
|
||||||
|
|
||||||
|
ps.mut.Lock()
|
||||||
|
defer ps.mut.Unlock()
|
||||||
|
|
||||||
|
p, ok := ps.presences[uID]
|
||||||
|
if ok {
|
||||||
|
return &p, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, store.ErrNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Presence) Presences(guildID discord.GuildID) ([]gateway.Presence, error) {
|
||||||
|
iv, ok := s.guilds.Load(guildID)
|
||||||
|
if !ok {
|
||||||
|
return nil, store.ErrNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
ps := iv.(*presences)
|
||||||
|
|
||||||
|
ps.mut.Lock()
|
||||||
|
defer ps.mut.Unlock()
|
||||||
|
|
||||||
|
var presences = make([]gateway.Presence, 0, len(ps.presences))
|
||||||
|
for _, p := range ps.presences {
|
||||||
|
presences = append(presences, p)
|
||||||
|
}
|
||||||
|
|
||||||
|
return presences, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Presence) PresenceSet(guildID discord.GuildID, presence gateway.Presence) error {
|
||||||
|
iv, _ := s.guilds.LoadOrStore(guildID)
|
||||||
|
|
||||||
|
ps := iv.(*presences)
|
||||||
|
|
||||||
|
ps.mut.Lock()
|
||||||
|
defer ps.mut.Unlock()
|
||||||
|
|
||||||
|
// Shitty if check is better than a realloc every time.
|
||||||
|
if ps.presences == nil {
|
||||||
|
ps.presences = make(map[discord.UserID]gateway.Presence, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
ps.presences[presence.User.ID] = presence
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Presence) PresenceRemove(guildID discord.GuildID, userID discord.UserID) error {
|
||||||
|
iv, ok := s.guilds.Load(guildID)
|
||||||
|
if !ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
ps := iv.(*presences)
|
||||||
|
|
||||||
|
ps.mut.Lock()
|
||||||
|
delete(ps.presences, userID)
|
||||||
|
ps.mut.Unlock()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
99
state/store/defaultstore/role.go
Normal file
99
state/store/defaultstore/role.go
Normal file
|
@ -0,0 +1,99 @@
|
||||||
|
package defaultstore
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/diamondburned/arikawa/v2/discord"
|
||||||
|
"github.com/diamondburned/arikawa/v2/internal/moreatomic"
|
||||||
|
"github.com/diamondburned/arikawa/v2/state/store"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Role struct {
|
||||||
|
guilds moreatomic.Map
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ store.RoleStore = (*Role)(nil)
|
||||||
|
|
||||||
|
type roles struct {
|
||||||
|
mut sync.Mutex
|
||||||
|
roles map[discord.RoleID]discord.Role
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewRole() *Role {
|
||||||
|
return &Role{
|
||||||
|
guilds: *moreatomic.NewMap(func() interface{} {
|
||||||
|
return &roles{
|
||||||
|
roles: make(map[discord.RoleID]discord.Role, 1),
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Role) Reset() error {
|
||||||
|
return s.guilds.Reset()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Role) Role(guildID discord.GuildID, roleID discord.RoleID) (*discord.Role, error) {
|
||||||
|
iv, ok := s.guilds.Load(guildID)
|
||||||
|
if !ok {
|
||||||
|
return nil, store.ErrNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
rs := iv.(*roles)
|
||||||
|
|
||||||
|
rs.mut.Lock()
|
||||||
|
defer rs.mut.Unlock()
|
||||||
|
|
||||||
|
r, ok := rs.roles[roleID]
|
||||||
|
if ok {
|
||||||
|
return &r, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, store.ErrNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Role) Roles(guildID discord.GuildID) ([]discord.Role, error) {
|
||||||
|
iv, ok := s.guilds.Load(guildID)
|
||||||
|
if !ok {
|
||||||
|
return nil, store.ErrNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
rs := iv.(*roles)
|
||||||
|
|
||||||
|
rs.mut.Lock()
|
||||||
|
defer rs.mut.Unlock()
|
||||||
|
|
||||||
|
var roles = make([]discord.Role, 0, len(rs.roles))
|
||||||
|
for _, role := range rs.roles {
|
||||||
|
roles = append(roles, role)
|
||||||
|
}
|
||||||
|
|
||||||
|
return roles, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Role) RoleSet(guildID discord.GuildID, role discord.Role) error {
|
||||||
|
iv, _ := s.guilds.LoadOrStore(guildID)
|
||||||
|
|
||||||
|
rs := iv.(*roles)
|
||||||
|
|
||||||
|
rs.mut.Lock()
|
||||||
|
rs.roles[role.ID] = role
|
||||||
|
rs.mut.Unlock()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Role) RoleRemove(guildID discord.GuildID, roleID discord.RoleID) error {
|
||||||
|
iv, ok := s.guilds.Load(guildID)
|
||||||
|
if !ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
rs := iv.(*roles)
|
||||||
|
|
||||||
|
rs.mut.Lock()
|
||||||
|
delete(rs.roles, roleID)
|
||||||
|
rs.mut.Unlock()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
101
state/store/defaultstore/voicestate.go
Normal file
101
state/store/defaultstore/voicestate.go
Normal file
|
@ -0,0 +1,101 @@
|
||||||
|
package defaultstore
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/diamondburned/arikawa/v2/discord"
|
||||||
|
"github.com/diamondburned/arikawa/v2/internal/moreatomic"
|
||||||
|
"github.com/diamondburned/arikawa/v2/state/store"
|
||||||
|
)
|
||||||
|
|
||||||
|
type VoiceState struct {
|
||||||
|
guilds moreatomic.Map
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ store.VoiceStateStore = (*VoiceState)(nil)
|
||||||
|
|
||||||
|
type voiceStates struct {
|
||||||
|
mut sync.Mutex
|
||||||
|
voiceStates map[discord.UserID]discord.VoiceState
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewVoiceState() *VoiceState {
|
||||||
|
return &VoiceState{
|
||||||
|
guilds: *moreatomic.NewMap(func() interface{} {
|
||||||
|
return &voiceStates{
|
||||||
|
voiceStates: make(map[discord.UserID]discord.VoiceState, 1),
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *VoiceState) Reset() error {
|
||||||
|
return s.guilds.Reset()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *VoiceState) VoiceState(
|
||||||
|
guildID discord.GuildID, userID discord.UserID) (*discord.VoiceState, error) {
|
||||||
|
|
||||||
|
iv, ok := s.guilds.Load(guildID)
|
||||||
|
if !ok {
|
||||||
|
return nil, store.ErrNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
vs := iv.(*voiceStates)
|
||||||
|
|
||||||
|
vs.mut.Lock()
|
||||||
|
defer vs.mut.Unlock()
|
||||||
|
|
||||||
|
v, ok := vs.voiceStates[userID]
|
||||||
|
if ok {
|
||||||
|
return &v, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, store.ErrNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *VoiceState) VoiceStates(guildID discord.GuildID) ([]discord.VoiceState, error) {
|
||||||
|
iv, ok := s.guilds.Load(guildID)
|
||||||
|
if !ok {
|
||||||
|
return nil, store.ErrNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
vs := iv.(*voiceStates)
|
||||||
|
|
||||||
|
vs.mut.Lock()
|
||||||
|
defer vs.mut.Unlock()
|
||||||
|
|
||||||
|
var states = make([]discord.VoiceState, 0, len(vs.voiceStates))
|
||||||
|
for _, state := range vs.voiceStates {
|
||||||
|
states = append(states, state)
|
||||||
|
}
|
||||||
|
|
||||||
|
return states, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *VoiceState) VoiceStateSet(guildID discord.GuildID, voiceState discord.VoiceState) error {
|
||||||
|
iv, _ := s.guilds.LoadOrStore(guildID)
|
||||||
|
|
||||||
|
vs := iv.(*voiceStates)
|
||||||
|
|
||||||
|
vs.mut.Lock()
|
||||||
|
vs.voiceStates[voiceState.UserID] = voiceState
|
||||||
|
vs.mut.Unlock()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *VoiceState) VoiceStateRemove(guildID discord.GuildID, userID discord.UserID) error {
|
||||||
|
iv, ok := s.guilds.Load(guildID)
|
||||||
|
if !ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
vs := iv.(*voiceStates)
|
||||||
|
|
||||||
|
vs.mut.Lock()
|
||||||
|
delete(vs.voiceStates, userID)
|
||||||
|
vs.mut.Unlock()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
375
state/store/store.go
Normal file
375
state/store/store.go
Normal file
|
@ -0,0 +1,375 @@
|
||||||
|
// Package store contains interfaces of the state's storage and its
|
||||||
|
// implementations.
|
||||||
|
//
|
||||||
|
// Getter Methods
|
||||||
|
//
|
||||||
|
// All getter methods will be wrapped by the State. If the State can't find
|
||||||
|
// anything in the storage, it will call the API itself and automatically add
|
||||||
|
// what's missing into the storage.
|
||||||
|
//
|
||||||
|
// Methods that return with a slice should pay attention to race conditions that
|
||||||
|
// would mutate the underlying slice (and as a result the returned slice as
|
||||||
|
// well). The best way to avoid this is to copy the whole slice, like
|
||||||
|
// defaultstore implementations do.
|
||||||
|
//
|
||||||
|
// Getter methods should not care about returning slices in order, unless
|
||||||
|
// explicitly stated against.
|
||||||
|
//
|
||||||
|
// ErrNotFound Rules
|
||||||
|
//
|
||||||
|
// If a getter method cannot find something, it should return ErrNotFound.
|
||||||
|
// Callers including State may check if the error is ErrNotFound to do something
|
||||||
|
// else. For example, if Guilds currently stores nothing, then it should return
|
||||||
|
// an empty slice and a nil error.
|
||||||
|
//
|
||||||
|
// In some cases, there may not be a way to know whether or not the store is
|
||||||
|
// unpopulated or is actually empty. In that case, implementations can return
|
||||||
|
// ErrNotFound when either happens. This will make State refetch from the API,
|
||||||
|
// so it is not ideal.
|
||||||
|
//
|
||||||
|
// Remove Methods
|
||||||
|
//
|
||||||
|
// Remove methods should return a nil error if the item it wants to delete is
|
||||||
|
// not found. This helps save some additional work in some cases.
|
||||||
|
package store
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/diamondburned/arikawa/v2/discord"
|
||||||
|
"github.com/diamondburned/arikawa/v2/gateway"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ErrNotFound is an error that a store can use to return when something isn't
|
||||||
|
// in the storage. There is no strict restrictions on what uses this (the
|
||||||
|
// default one does, though), so be advised.
|
||||||
|
var ErrNotFound = errors.New("item not found in store")
|
||||||
|
|
||||||
|
// Cabinet combines all store interfaces into one but allows swapping individual
|
||||||
|
// stores out for another. Since the struct only consists of interfaces, it can
|
||||||
|
// be copied around.
|
||||||
|
type Cabinet struct {
|
||||||
|
MeStore
|
||||||
|
ChannelStore
|
||||||
|
EmojiStore
|
||||||
|
GuildStore
|
||||||
|
MemberStore
|
||||||
|
MessageStore
|
||||||
|
PresenceStore
|
||||||
|
RoleStore
|
||||||
|
VoiceStateStore
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset resets everything inside the container.
|
||||||
|
func (sc *Cabinet) Reset() error {
|
||||||
|
errors := []error{
|
||||||
|
sc.MeStore.Reset(),
|
||||||
|
sc.ChannelStore.Reset(),
|
||||||
|
sc.EmojiStore.Reset(),
|
||||||
|
sc.GuildStore.Reset(),
|
||||||
|
sc.MemberStore.Reset(),
|
||||||
|
sc.MessageStore.Reset(),
|
||||||
|
sc.PresenceStore.Reset(),
|
||||||
|
sc.RoleStore.Reset(),
|
||||||
|
sc.VoiceStateStore.Reset(),
|
||||||
|
}
|
||||||
|
|
||||||
|
nonNils := errors[:0]
|
||||||
|
|
||||||
|
for _, err := range errors {
|
||||||
|
if err != nil {
|
||||||
|
nonNils = append(nonNils, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(nonNils) > 0 {
|
||||||
|
return ResetErrors(nonNils)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResetErrors represents the multiple errors when StoreContainer is being
|
||||||
|
// resetted. A ResetErrors value must have at least 1 error.
|
||||||
|
type ResetErrors []error
|
||||||
|
|
||||||
|
// Error formats ResetErrors, showing the number of errors and the last error.
|
||||||
|
func (errs ResetErrors) Error() string {
|
||||||
|
return fmt.Sprintf(
|
||||||
|
"encountered %d reset errors (last: %v)",
|
||||||
|
len(errs), errs[len(errs)-1],
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unwrap returns the last error in the list.
|
||||||
|
func (errs ResetErrors) Unwrap() error {
|
||||||
|
return errs[len(errs)-1]
|
||||||
|
}
|
||||||
|
|
||||||
|
// append adds the error only if it is not nil.
|
||||||
|
func (errs *ResetErrors) append(err error) {
|
||||||
|
if err != nil {
|
||||||
|
*errs = append(*errs, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Noop is a no-op implementation of all store interfaces. Its getters will
|
||||||
|
// always return ErrNotFound, and its setters will never return an error.
|
||||||
|
var Noop = noop{}
|
||||||
|
|
||||||
|
// NoopCabinet is a store cabinet with all store methods set to the Noop
|
||||||
|
// implementations.
|
||||||
|
var NoopCabinet = Cabinet{
|
||||||
|
MeStore: Noop,
|
||||||
|
ChannelStore: Noop,
|
||||||
|
EmojiStore: Noop,
|
||||||
|
GuildStore: Noop,
|
||||||
|
MemberStore: Noop,
|
||||||
|
MessageStore: Noop,
|
||||||
|
PresenceStore: Noop,
|
||||||
|
RoleStore: Noop,
|
||||||
|
VoiceStateStore: Noop,
|
||||||
|
}
|
||||||
|
|
||||||
|
// noop is the Noop type that implements methods.
|
||||||
|
type noop struct{}
|
||||||
|
|
||||||
|
// Resetter is an interface to reset the store on every Ready event.
|
||||||
|
type Resetter interface {
|
||||||
|
// Reset resets the store to a new valid instance.
|
||||||
|
Reset() error
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ Resetter = (*noop)(nil)
|
||||||
|
|
||||||
|
func (noop) Reset() error { return nil }
|
||||||
|
|
||||||
|
// MeStore is the store interface for the current user.
|
||||||
|
type MeStore interface {
|
||||||
|
Resetter
|
||||||
|
|
||||||
|
Me() (*discord.User, error)
|
||||||
|
MyselfSet(me discord.User) error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (noop) Me() (*discord.User, error) { return nil, ErrNotFound }
|
||||||
|
func (noop) MyselfSet(discord.User) error { return nil }
|
||||||
|
|
||||||
|
// ChannelStore is the store interface for all channels.
|
||||||
|
type ChannelStore interface {
|
||||||
|
Resetter
|
||||||
|
|
||||||
|
// ChannelStore searches for both DM and guild channels.
|
||||||
|
Channel(discord.ChannelID) (*discord.Channel, error)
|
||||||
|
// CreatePrivateChannelStore searches for private channels by the recipient ID.
|
||||||
|
// It has the same API as *api.Client does.
|
||||||
|
CreatePrivateChannel(recipient discord.UserID) (*discord.Channel, error)
|
||||||
|
|
||||||
|
// Channels returns only channels from a guild.
|
||||||
|
Channels(discord.GuildID) ([]discord.Channel, error)
|
||||||
|
// PrivateChannels returns all private channels from the state.
|
||||||
|
PrivateChannels() ([]discord.Channel, error)
|
||||||
|
|
||||||
|
// Both ChannelSet and ChannelRemove should switch on Type to know if it's a
|
||||||
|
// private channel or not.
|
||||||
|
|
||||||
|
ChannelSet(discord.Channel) error
|
||||||
|
ChannelRemove(discord.Channel) error
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ ChannelStore = (*noop)(nil)
|
||||||
|
|
||||||
|
func (noop) Channel(discord.ChannelID) (*discord.Channel, error) {
|
||||||
|
return nil, ErrNotFound
|
||||||
|
}
|
||||||
|
func (noop) CreatePrivateChannel(discord.UserID) (*discord.Channel, error) {
|
||||||
|
return nil, ErrNotFound
|
||||||
|
}
|
||||||
|
func (noop) Channels(discord.GuildID) ([]discord.Channel, error) {
|
||||||
|
return nil, ErrNotFound
|
||||||
|
}
|
||||||
|
func (noop) PrivateChannels() ([]discord.Channel, error) {
|
||||||
|
return nil, ErrNotFound
|
||||||
|
}
|
||||||
|
func (noop) ChannelSet(discord.Channel) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
func (noop) ChannelRemove(discord.Channel) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// EmojiStore is the store interface for all emojis.
|
||||||
|
type EmojiStore interface {
|
||||||
|
Resetter
|
||||||
|
|
||||||
|
Emoji(discord.GuildID, discord.EmojiID) (*discord.Emoji, error)
|
||||||
|
Emojis(discord.GuildID) ([]discord.Emoji, error)
|
||||||
|
|
||||||
|
// EmojiSet should delete all old emojis before setting new ones. The given
|
||||||
|
// emojis slice will be a complete list of all emojis.
|
||||||
|
EmojiSet(discord.GuildID, []discord.Emoji) error
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ EmojiStore = (*noop)(nil)
|
||||||
|
|
||||||
|
func (noop) Emoji(discord.GuildID, discord.EmojiID) (*discord.Emoji, error) {
|
||||||
|
return nil, ErrNotFound
|
||||||
|
}
|
||||||
|
func (noop) Emojis(discord.GuildID) ([]discord.Emoji, error) {
|
||||||
|
return nil, ErrNotFound
|
||||||
|
}
|
||||||
|
func (noop) EmojiSet(discord.GuildID, []discord.Emoji) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GuildStore is the store interface for all guilds.
|
||||||
|
type GuildStore interface {
|
||||||
|
Resetter
|
||||||
|
|
||||||
|
Guild(discord.GuildID) (*discord.Guild, error)
|
||||||
|
Guilds() ([]discord.Guild, error)
|
||||||
|
|
||||||
|
GuildSet(discord.Guild) error
|
||||||
|
GuildRemove(id discord.GuildID) error
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ GuildStore = (*noop)(nil)
|
||||||
|
|
||||||
|
func (noop) Guild(discord.GuildID) (*discord.Guild, error) { return nil, ErrNotFound }
|
||||||
|
func (noop) Guilds() ([]discord.Guild, error) { return nil, ErrNotFound }
|
||||||
|
func (noop) GuildSet(discord.Guild) error { return nil }
|
||||||
|
func (noop) GuildRemove(discord.GuildID) error { return nil }
|
||||||
|
|
||||||
|
// MemberStore is the store interface for all members.
|
||||||
|
type MemberStore interface {
|
||||||
|
Resetter
|
||||||
|
|
||||||
|
Member(discord.GuildID, discord.UserID) (*discord.Member, error)
|
||||||
|
Members(discord.GuildID) ([]discord.Member, error)
|
||||||
|
|
||||||
|
MemberSet(discord.GuildID, discord.Member) error
|
||||||
|
MemberRemove(discord.GuildID, discord.UserID) error
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ MemberStore = (*noop)(nil)
|
||||||
|
|
||||||
|
func (noop) Member(discord.GuildID, discord.UserID) (*discord.Member, error) {
|
||||||
|
return nil, ErrNotFound
|
||||||
|
}
|
||||||
|
func (noop) Members(discord.GuildID) ([]discord.Member, error) {
|
||||||
|
return nil, ErrNotFound
|
||||||
|
}
|
||||||
|
func (noop) MemberSet(discord.GuildID, discord.Member) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
func (noop) MemberRemove(discord.GuildID, discord.UserID) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// MessageStore is the store interface for all messages.
|
||||||
|
type MessageStore interface {
|
||||||
|
Resetter
|
||||||
|
|
||||||
|
// MaxMessages returns the maximum number of messages. It is used to know if
|
||||||
|
// the state cache is filled or not for one channel
|
||||||
|
MaxMessages() int
|
||||||
|
|
||||||
|
Message(discord.ChannelID, discord.MessageID) (*discord.Message, error)
|
||||||
|
// Messages should return messages ordered from latest to earliest.
|
||||||
|
Messages(discord.ChannelID) ([]discord.Message, error)
|
||||||
|
|
||||||
|
// MessageSet should prepend messages into the slice, the latest being in
|
||||||
|
// front.
|
||||||
|
MessageSet(discord.Message) error
|
||||||
|
MessageRemove(discord.ChannelID, discord.MessageID) error
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ MessageStore = (*noop)(nil)
|
||||||
|
|
||||||
|
func (noop) MaxMessages() int {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
func (noop) Message(discord.ChannelID, discord.MessageID) (*discord.Message, error) {
|
||||||
|
return nil, ErrNotFound
|
||||||
|
}
|
||||||
|
func (noop) Messages(discord.ChannelID) ([]discord.Message, error) {
|
||||||
|
return nil, ErrNotFound
|
||||||
|
}
|
||||||
|
func (noop) MessageSet(discord.Message) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
func (noop) MessageRemove(discord.ChannelID, discord.MessageID) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// PresenceStore is the store interface for all user presences. Presences don't get
|
||||||
|
// fetched from the API; they will only be updated through the Gateway.
|
||||||
|
type PresenceStore interface {
|
||||||
|
Resetter
|
||||||
|
|
||||||
|
Presence(discord.GuildID, discord.UserID) (*gateway.Presence, error)
|
||||||
|
Presences(discord.GuildID) ([]gateway.Presence, error)
|
||||||
|
|
||||||
|
PresenceSet(discord.GuildID, gateway.Presence) error
|
||||||
|
PresenceRemove(discord.GuildID, discord.UserID) error
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ PresenceStore = (*noop)(nil)
|
||||||
|
|
||||||
|
func (noop) Presence(discord.GuildID, discord.UserID) (*gateway.Presence, error) {
|
||||||
|
return nil, ErrNotFound
|
||||||
|
}
|
||||||
|
func (noop) Presences(discord.GuildID) ([]gateway.Presence, error) {
|
||||||
|
return nil, ErrNotFound
|
||||||
|
}
|
||||||
|
func (noop) PresenceSet(discord.GuildID, gateway.Presence) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
func (noop) PresenceRemove(discord.GuildID, discord.UserID) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// RoleStore is the store interface for all member roles.
|
||||||
|
type RoleStore interface {
|
||||||
|
Resetter
|
||||||
|
|
||||||
|
Role(discord.GuildID, discord.RoleID) (*discord.Role, error)
|
||||||
|
Roles(discord.GuildID) ([]discord.Role, error)
|
||||||
|
|
||||||
|
RoleSet(discord.GuildID, discord.Role) error
|
||||||
|
RoleRemove(discord.GuildID, discord.RoleID) error
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ RoleStore = (*noop)(nil)
|
||||||
|
|
||||||
|
func (noop) Role(discord.GuildID, discord.RoleID) (*discord.Role, error) { return nil, ErrNotFound }
|
||||||
|
func (noop) Roles(discord.GuildID) ([]discord.Role, error) { return nil, ErrNotFound }
|
||||||
|
func (noop) RoleSet(discord.GuildID, discord.Role) error { return nil }
|
||||||
|
func (noop) RoleRemove(discord.GuildID, discord.RoleID) error { return nil }
|
||||||
|
|
||||||
|
// VoiceStateStore is the store interface for all voice states.
|
||||||
|
type VoiceStateStore interface {
|
||||||
|
Resetter
|
||||||
|
|
||||||
|
VoiceState(discord.GuildID, discord.UserID) (*discord.VoiceState, error)
|
||||||
|
VoiceStates(discord.GuildID) ([]discord.VoiceState, error)
|
||||||
|
|
||||||
|
VoiceStateSet(discord.GuildID, discord.VoiceState) error
|
||||||
|
VoiceStateRemove(discord.GuildID, discord.UserID) error
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ VoiceStateStore = (*noop)(nil)
|
||||||
|
|
||||||
|
func (noop) VoiceState(discord.GuildID, discord.UserID) (*discord.VoiceState, error) {
|
||||||
|
return nil, ErrNotFound
|
||||||
|
}
|
||||||
|
func (noop) VoiceStates(discord.GuildID) ([]discord.VoiceState, error) {
|
||||||
|
return nil, ErrNotFound
|
||||||
|
}
|
||||||
|
func (noop) VoiceStateSet(discord.GuildID, discord.VoiceState) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
func (noop) VoiceStateRemove(discord.GuildID, discord.UserID) error {
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -1,702 +0,0 @@
|
||||||
package state
|
|
||||||
|
|
||||||
import (
|
|
||||||
"sync"
|
|
||||||
|
|
||||||
"github.com/diamondburned/arikawa/v2/discord"
|
|
||||||
)
|
|
||||||
|
|
||||||
// TODO: make an ExpiryStore
|
|
||||||
|
|
||||||
type DefaultStore struct {
|
|
||||||
DefaultStoreOptions
|
|
||||||
|
|
||||||
self discord.User
|
|
||||||
|
|
||||||
// includes normal and private
|
|
||||||
privates map[discord.ChannelID]discord.Channel
|
|
||||||
guilds map[discord.GuildID]discord.Guild
|
|
||||||
|
|
||||||
roles map[discord.GuildID][]discord.Role
|
|
||||||
emojis map[discord.GuildID][]discord.Emoji
|
|
||||||
channels map[discord.GuildID][]discord.Channel
|
|
||||||
presences map[discord.GuildID][]discord.Presence
|
|
||||||
voiceStates map[discord.GuildID][]discord.VoiceState
|
|
||||||
messages map[discord.ChannelID][]discord.Message
|
|
||||||
|
|
||||||
// special case; optimize for lots of members
|
|
||||||
members map[discord.GuildID]map[discord.UserID]discord.Member
|
|
||||||
|
|
||||||
mut sync.RWMutex
|
|
||||||
}
|
|
||||||
|
|
||||||
type DefaultStoreOptions struct {
|
|
||||||
MaxMessages uint // default 50
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ Store = (*DefaultStore)(nil)
|
|
||||||
|
|
||||||
func NewDefaultStore(opts *DefaultStoreOptions) *DefaultStore {
|
|
||||||
if opts == nil {
|
|
||||||
opts = &DefaultStoreOptions{
|
|
||||||
MaxMessages: 50,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ds := &DefaultStore{DefaultStoreOptions: *opts}
|
|
||||||
ds.Reset()
|
|
||||||
|
|
||||||
return ds
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *DefaultStore) Reset() error {
|
|
||||||
s.mut.Lock()
|
|
||||||
defer s.mut.Unlock()
|
|
||||||
|
|
||||||
s.self = discord.User{}
|
|
||||||
|
|
||||||
s.privates = map[discord.ChannelID]discord.Channel{}
|
|
||||||
s.guilds = map[discord.GuildID]discord.Guild{}
|
|
||||||
|
|
||||||
s.roles = map[discord.GuildID][]discord.Role{}
|
|
||||||
s.emojis = map[discord.GuildID][]discord.Emoji{}
|
|
||||||
s.channels = map[discord.GuildID][]discord.Channel{}
|
|
||||||
s.presences = map[discord.GuildID][]discord.Presence{}
|
|
||||||
s.voiceStates = map[discord.GuildID][]discord.VoiceState{}
|
|
||||||
s.messages = map[discord.ChannelID][]discord.Message{}
|
|
||||||
|
|
||||||
s.members = map[discord.GuildID]map[discord.UserID]discord.Member{}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
////
|
|
||||||
|
|
||||||
func (s *DefaultStore) Me() (*discord.User, error) {
|
|
||||||
s.mut.RLock()
|
|
||||||
defer s.mut.RUnlock()
|
|
||||||
|
|
||||||
if !s.self.ID.IsValid() {
|
|
||||||
return nil, ErrStoreNotFound
|
|
||||||
}
|
|
||||||
|
|
||||||
return &s.self, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *DefaultStore) MyselfSet(me discord.User) error {
|
|
||||||
s.mut.Lock()
|
|
||||||
s.self = me
|
|
||||||
s.mut.Unlock()
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
////
|
|
||||||
|
|
||||||
func (s *DefaultStore) Channel(id discord.ChannelID) (*discord.Channel, error) {
|
|
||||||
s.mut.RLock()
|
|
||||||
defer s.mut.RUnlock()
|
|
||||||
|
|
||||||
if ch, ok := s.privates[id]; ok {
|
|
||||||
// implicit copy
|
|
||||||
return &ch, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, chs := range s.channels {
|
|
||||||
for _, ch := range chs {
|
|
||||||
if ch.ID == id {
|
|
||||||
return &ch, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, ErrStoreNotFound
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *DefaultStore) Channels(guildID discord.GuildID) ([]discord.Channel, error) {
|
|
||||||
s.mut.RLock()
|
|
||||||
defer s.mut.RUnlock()
|
|
||||||
|
|
||||||
chs, ok := s.channels[guildID]
|
|
||||||
if !ok {
|
|
||||||
return nil, ErrStoreNotFound
|
|
||||||
}
|
|
||||||
|
|
||||||
return append([]discord.Channel{}, chs...), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreatePrivateChannel searches in the cache for a private channel. It makes no
|
|
||||||
// API calls.
|
|
||||||
func (s *DefaultStore) CreatePrivateChannel(recipient discord.UserID) (*discord.Channel, error) {
|
|
||||||
s.mut.RLock()
|
|
||||||
defer s.mut.RUnlock()
|
|
||||||
|
|
||||||
// slow way
|
|
||||||
for _, ch := range s.privates {
|
|
||||||
if ch.Type != discord.DirectMessage || len(ch.DMRecipients) == 0 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if ch.DMRecipients[0].ID == recipient {
|
|
||||||
// Return an implicit copy made by range.
|
|
||||||
return &ch, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil, ErrStoreNotFound
|
|
||||||
}
|
|
||||||
|
|
||||||
// PrivateChannels returns a list of Direct Message channels randomly ordered.
|
|
||||||
func (s *DefaultStore) PrivateChannels() ([]discord.Channel, error) {
|
|
||||||
s.mut.RLock()
|
|
||||||
defer s.mut.RUnlock()
|
|
||||||
|
|
||||||
var chs = make([]discord.Channel, 0, len(s.privates))
|
|
||||||
for i := range s.privates {
|
|
||||||
chs = append(chs, s.privates[i])
|
|
||||||
}
|
|
||||||
|
|
||||||
return chs, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *DefaultStore) ChannelSet(channel discord.Channel) error {
|
|
||||||
s.mut.Lock()
|
|
||||||
defer s.mut.Unlock()
|
|
||||||
|
|
||||||
if !channel.GuildID.IsValid() {
|
|
||||||
s.privates[channel.ID] = channel
|
|
||||||
|
|
||||||
} else {
|
|
||||||
chs := s.channels[channel.GuildID]
|
|
||||||
|
|
||||||
for i, ch := range chs {
|
|
||||||
if ch.ID == channel.ID {
|
|
||||||
// Also from discordgo.
|
|
||||||
if channel.Permissions == nil {
|
|
||||||
channel.Permissions = ch.Permissions
|
|
||||||
}
|
|
||||||
|
|
||||||
// Found, just edit
|
|
||||||
chs[i] = channel
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
chs = append(chs, channel)
|
|
||||||
s.channels[channel.GuildID] = chs
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *DefaultStore) ChannelRemove(channel discord.Channel) error {
|
|
||||||
s.mut.Lock()
|
|
||||||
defer s.mut.Unlock()
|
|
||||||
|
|
||||||
chs, ok := s.channels[channel.GuildID]
|
|
||||||
if !ok {
|
|
||||||
return ErrStoreNotFound
|
|
||||||
}
|
|
||||||
|
|
||||||
for i, ch := range chs {
|
|
||||||
if ch.ID == channel.ID {
|
|
||||||
// Fast unordered delete.
|
|
||||||
chs[i] = chs[len(chs)-1]
|
|
||||||
chs = chs[:len(chs)-1]
|
|
||||||
|
|
||||||
s.channels[channel.GuildID] = chs
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return ErrStoreNotFound
|
|
||||||
}
|
|
||||||
|
|
||||||
////
|
|
||||||
|
|
||||||
func (s *DefaultStore) Emoji(guildID discord.GuildID, emojiID discord.EmojiID) (*discord.Emoji, error) {
|
|
||||||
s.mut.RLock()
|
|
||||||
defer s.mut.RUnlock()
|
|
||||||
|
|
||||||
emojis, ok := s.emojis[guildID]
|
|
||||||
if !ok {
|
|
||||||
return nil, ErrStoreNotFound
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, emoji := range emojis {
|
|
||||||
if emoji.ID == emojiID {
|
|
||||||
// Emoji is an implicit copy, so we could do this safely.
|
|
||||||
return &emoji, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, ErrStoreNotFound
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *DefaultStore) Emojis(guildID discord.GuildID) ([]discord.Emoji, error) {
|
|
||||||
s.mut.RLock()
|
|
||||||
defer s.mut.RUnlock()
|
|
||||||
|
|
||||||
emojis, ok := s.emojis[guildID]
|
|
||||||
if !ok {
|
|
||||||
return nil, ErrStoreNotFound
|
|
||||||
}
|
|
||||||
|
|
||||||
return append([]discord.Emoji{}, emojis...), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *DefaultStore) EmojiSet(guildID discord.GuildID, emojis []discord.Emoji) error {
|
|
||||||
s.mut.Lock()
|
|
||||||
defer s.mut.Unlock()
|
|
||||||
|
|
||||||
// A nil slice is acceptable, as we'll make a new slice later on and set it.
|
|
||||||
s.emojis[guildID] = emojis
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
////
|
|
||||||
|
|
||||||
func (s *DefaultStore) Guild(id discord.GuildID) (*discord.Guild, error) {
|
|
||||||
s.mut.RLock()
|
|
||||||
defer s.mut.RUnlock()
|
|
||||||
|
|
||||||
ch, ok := s.guilds[id]
|
|
||||||
if !ok {
|
|
||||||
return nil, ErrStoreNotFound
|
|
||||||
}
|
|
||||||
|
|
||||||
// implicit copy
|
|
||||||
return &ch, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *DefaultStore) Guilds() ([]discord.Guild, error) {
|
|
||||||
s.mut.RLock()
|
|
||||||
defer s.mut.RUnlock()
|
|
||||||
|
|
||||||
if len(s.guilds) == 0 {
|
|
||||||
return nil, ErrStoreNotFound
|
|
||||||
}
|
|
||||||
|
|
||||||
var gs = make([]discord.Guild, 0, len(s.guilds))
|
|
||||||
for _, g := range s.guilds {
|
|
||||||
gs = append(gs, g)
|
|
||||||
}
|
|
||||||
|
|
||||||
return gs, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *DefaultStore) GuildSet(guild discord.Guild) error {
|
|
||||||
s.mut.Lock()
|
|
||||||
defer s.mut.Unlock()
|
|
||||||
|
|
||||||
s.guilds[guild.ID] = guild
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *DefaultStore) GuildRemove(id discord.GuildID) error {
|
|
||||||
s.mut.Lock()
|
|
||||||
defer s.mut.Unlock()
|
|
||||||
|
|
||||||
if _, ok := s.guilds[id]; !ok {
|
|
||||||
return ErrStoreNotFound
|
|
||||||
}
|
|
||||||
|
|
||||||
delete(s.guilds, id)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
////
|
|
||||||
|
|
||||||
func (s *DefaultStore) Member(
|
|
||||||
guildID discord.GuildID, userID discord.UserID) (*discord.Member, error) {
|
|
||||||
|
|
||||||
s.mut.RLock()
|
|
||||||
defer s.mut.RUnlock()
|
|
||||||
|
|
||||||
ms, ok := s.members[guildID]
|
|
||||||
if !ok {
|
|
||||||
return nil, ErrStoreNotFound
|
|
||||||
}
|
|
||||||
|
|
||||||
m, ok := ms[userID]
|
|
||||||
if ok {
|
|
||||||
return &m, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, ErrStoreNotFound
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *DefaultStore) Members(guildID discord.GuildID) ([]discord.Member, error) {
|
|
||||||
s.mut.RLock()
|
|
||||||
defer s.mut.RUnlock()
|
|
||||||
|
|
||||||
ms, ok := s.members[guildID]
|
|
||||||
if !ok {
|
|
||||||
return nil, ErrStoreNotFound
|
|
||||||
}
|
|
||||||
|
|
||||||
var members = make([]discord.Member, 0, len(ms))
|
|
||||||
for _, m := range ms {
|
|
||||||
members = append(members, m)
|
|
||||||
}
|
|
||||||
|
|
||||||
return members, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *DefaultStore) MemberSet(guildID discord.GuildID, member discord.Member) error {
|
|
||||||
s.mut.Lock()
|
|
||||||
defer s.mut.Unlock()
|
|
||||||
|
|
||||||
ms, ok := s.members[guildID]
|
|
||||||
if !ok {
|
|
||||||
ms = make(map[discord.UserID]discord.Member, 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
ms[member.User.ID] = member
|
|
||||||
s.members[guildID] = ms
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *DefaultStore) MemberRemove(guildID discord.GuildID, userID discord.UserID) error {
|
|
||||||
s.mut.Lock()
|
|
||||||
defer s.mut.Unlock()
|
|
||||||
|
|
||||||
ms, ok := s.members[guildID]
|
|
||||||
if !ok {
|
|
||||||
return ErrStoreNotFound
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, ok := ms[userID]; !ok {
|
|
||||||
return ErrStoreNotFound
|
|
||||||
}
|
|
||||||
|
|
||||||
delete(ms, userID)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
////
|
|
||||||
|
|
||||||
func (s *DefaultStore) Message(
|
|
||||||
channelID discord.ChannelID, messageID discord.MessageID) (*discord.Message, error) {
|
|
||||||
|
|
||||||
s.mut.RLock()
|
|
||||||
defer s.mut.RUnlock()
|
|
||||||
|
|
||||||
ms, ok := s.messages[channelID]
|
|
||||||
if !ok {
|
|
||||||
return nil, ErrStoreNotFound
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, m := range ms {
|
|
||||||
if m.ID == messageID {
|
|
||||||
return &m, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, ErrStoreNotFound
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *DefaultStore) Messages(channelID discord.ChannelID) ([]discord.Message, error) {
|
|
||||||
s.mut.RLock()
|
|
||||||
defer s.mut.RUnlock()
|
|
||||||
|
|
||||||
ms, ok := s.messages[channelID]
|
|
||||||
if !ok {
|
|
||||||
return nil, ErrStoreNotFound
|
|
||||||
}
|
|
||||||
|
|
||||||
return append([]discord.Message{}, ms...), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *DefaultStore) MaxMessages() int {
|
|
||||||
return int(s.DefaultStoreOptions.MaxMessages)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *DefaultStore) MessageSet(message discord.Message) error {
|
|
||||||
s.mut.Lock()
|
|
||||||
defer s.mut.Unlock()
|
|
||||||
|
|
||||||
ms, ok := s.messages[message.ChannelID]
|
|
||||||
if !ok {
|
|
||||||
ms = make([]discord.Message, 0, s.MaxMessages()+1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if we already have the message.
|
|
||||||
for i, m := range ms {
|
|
||||||
if m.ID == message.ID {
|
|
||||||
DiffMessage(message, &m)
|
|
||||||
ms[i] = m
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Order: latest to earliest, similar to the API.
|
|
||||||
|
|
||||||
var end = len(ms)
|
|
||||||
if max := s.MaxMessages(); end >= max {
|
|
||||||
// If the end (length) is larger than the maximum amount, then cap it.
|
|
||||||
end = max
|
|
||||||
} else {
|
|
||||||
// Else, append an empty message to the end.
|
|
||||||
ms = append(ms, discord.Message{})
|
|
||||||
// Increment to update the length.
|
|
||||||
end++
|
|
||||||
}
|
|
||||||
|
|
||||||
// Copy hack to prepend. This copies the 0th-(end-1)th entries to
|
|
||||||
// 1st-endth.
|
|
||||||
copy(ms[1:end], ms[0:end-1])
|
|
||||||
// Then, set the 0th entry.
|
|
||||||
ms[0] = message
|
|
||||||
|
|
||||||
s.messages[message.ChannelID] = ms
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *DefaultStore) MessageRemove(
|
|
||||||
channelID discord.ChannelID, messageID discord.MessageID) error {
|
|
||||||
|
|
||||||
s.mut.Lock()
|
|
||||||
defer s.mut.Unlock()
|
|
||||||
|
|
||||||
ms, ok := s.messages[channelID]
|
|
||||||
if !ok {
|
|
||||||
return ErrStoreNotFound
|
|
||||||
}
|
|
||||||
|
|
||||||
for i, m := range ms {
|
|
||||||
if m.ID == messageID {
|
|
||||||
ms = append(ms[:i], ms[i+1:]...)
|
|
||||||
s.messages[channelID] = ms
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return ErrStoreNotFound
|
|
||||||
}
|
|
||||||
|
|
||||||
////
|
|
||||||
|
|
||||||
func (s *DefaultStore) Presence(
|
|
||||||
guildID discord.GuildID, userID discord.UserID) (*discord.Presence, error) {
|
|
||||||
|
|
||||||
s.mut.RLock()
|
|
||||||
defer s.mut.RUnlock()
|
|
||||||
|
|
||||||
ps, ok := s.presences[guildID]
|
|
||||||
if !ok {
|
|
||||||
return nil, ErrStoreNotFound
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, p := range ps {
|
|
||||||
if p.User.ID == userID {
|
|
||||||
return &p, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, ErrStoreNotFound
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *DefaultStore) Presences(guildID discord.GuildID) ([]discord.Presence, error) {
|
|
||||||
s.mut.RLock()
|
|
||||||
defer s.mut.RUnlock()
|
|
||||||
|
|
||||||
ps, ok := s.presences[guildID]
|
|
||||||
if !ok {
|
|
||||||
return nil, ErrStoreNotFound
|
|
||||||
}
|
|
||||||
|
|
||||||
return append([]discord.Presence{}, ps...), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *DefaultStore) PresenceSet(guildID discord.GuildID, presence discord.Presence) error {
|
|
||||||
s.mut.Lock()
|
|
||||||
defer s.mut.Unlock()
|
|
||||||
|
|
||||||
ps, _ := s.presences[guildID]
|
|
||||||
|
|
||||||
for i, p := range ps {
|
|
||||||
if p.User.ID == presence.User.ID {
|
|
||||||
// Change the backing array.
|
|
||||||
ps[i] = presence
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ps = append(ps, presence)
|
|
||||||
s.presences[guildID] = ps
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *DefaultStore) PresenceRemove(guildID discord.GuildID, userID discord.UserID) error {
|
|
||||||
s.mut.Lock()
|
|
||||||
defer s.mut.Unlock()
|
|
||||||
|
|
||||||
ps, ok := s.presences[guildID]
|
|
||||||
if !ok {
|
|
||||||
return ErrStoreNotFound
|
|
||||||
}
|
|
||||||
|
|
||||||
for i, p := range ps {
|
|
||||||
if p.User.ID == userID {
|
|
||||||
ps[i] = ps[len(ps)-1]
|
|
||||||
ps = ps[:len(ps)-1]
|
|
||||||
|
|
||||||
s.presences[guildID] = ps
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return ErrStoreNotFound
|
|
||||||
}
|
|
||||||
|
|
||||||
////
|
|
||||||
|
|
||||||
func (s *DefaultStore) Role(guildID discord.GuildID, roleID discord.RoleID) (*discord.Role, error) {
|
|
||||||
s.mut.RLock()
|
|
||||||
defer s.mut.RUnlock()
|
|
||||||
|
|
||||||
rs, ok := s.roles[guildID]
|
|
||||||
if !ok {
|
|
||||||
return nil, ErrStoreNotFound
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, r := range rs {
|
|
||||||
if r.ID == roleID {
|
|
||||||
return &r, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, ErrStoreNotFound
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *DefaultStore) Roles(guildID discord.GuildID) ([]discord.Role, error) {
|
|
||||||
s.mut.RLock()
|
|
||||||
defer s.mut.RUnlock()
|
|
||||||
|
|
||||||
rs, ok := s.roles[guildID]
|
|
||||||
if !ok {
|
|
||||||
return nil, ErrStoreNotFound
|
|
||||||
}
|
|
||||||
|
|
||||||
return append([]discord.Role{}, rs...), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *DefaultStore) RoleSet(guildID discord.GuildID, role discord.Role) error {
|
|
||||||
s.mut.Lock()
|
|
||||||
defer s.mut.Unlock()
|
|
||||||
|
|
||||||
// A nil slice is fine, since we can just append the role.
|
|
||||||
rs, _ := s.roles[guildID]
|
|
||||||
|
|
||||||
for i, r := range rs {
|
|
||||||
if r.ID == role.ID {
|
|
||||||
// This changes the backing array, so we don't need to reset the
|
|
||||||
// slice.
|
|
||||||
rs[i] = role
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
rs = append(rs, role)
|
|
||||||
s.roles[guildID] = rs
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *DefaultStore) RoleRemove(guildID discord.GuildID, roleID discord.RoleID) error {
|
|
||||||
s.mut.Lock()
|
|
||||||
defer s.mut.Unlock()
|
|
||||||
|
|
||||||
rs, ok := s.roles[guildID]
|
|
||||||
if !ok {
|
|
||||||
return ErrStoreNotFound
|
|
||||||
}
|
|
||||||
|
|
||||||
for i, r := range rs {
|
|
||||||
if r.ID == roleID {
|
|
||||||
// Fast delete.
|
|
||||||
rs[i] = rs[len(rs)-1]
|
|
||||||
rs = rs[:len(rs)-1]
|
|
||||||
|
|
||||||
s.roles[guildID] = rs
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return ErrStoreNotFound
|
|
||||||
}
|
|
||||||
|
|
||||||
////
|
|
||||||
|
|
||||||
func (s *DefaultStore) VoiceState(
|
|
||||||
guildID discord.GuildID, userID discord.UserID) (*discord.VoiceState, error) {
|
|
||||||
|
|
||||||
s.mut.RLock()
|
|
||||||
defer s.mut.RUnlock()
|
|
||||||
|
|
||||||
states, ok := s.voiceStates[guildID]
|
|
||||||
if !ok {
|
|
||||||
return nil, ErrStoreNotFound
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, vs := range states {
|
|
||||||
if vs.UserID == userID {
|
|
||||||
return &vs, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, ErrStoreNotFound
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *DefaultStore) VoiceStates(guildID discord.GuildID) ([]discord.VoiceState, error) {
|
|
||||||
s.mut.RLock()
|
|
||||||
defer s.mut.RUnlock()
|
|
||||||
|
|
||||||
states, ok := s.voiceStates[guildID]
|
|
||||||
if !ok {
|
|
||||||
return nil, ErrStoreNotFound
|
|
||||||
}
|
|
||||||
|
|
||||||
return append([]discord.VoiceState{}, states...), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *DefaultStore) VoiceStateSet(guildID discord.GuildID, voiceState discord.VoiceState) error {
|
|
||||||
s.mut.Lock()
|
|
||||||
defer s.mut.Unlock()
|
|
||||||
|
|
||||||
states, _ := s.voiceStates[guildID]
|
|
||||||
|
|
||||||
for i, vs := range states {
|
|
||||||
if vs.UserID == voiceState.UserID {
|
|
||||||
// change the backing array
|
|
||||||
states[i] = voiceState
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
states = append(states, voiceState)
|
|
||||||
s.voiceStates[guildID] = states
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *DefaultStore) VoiceStateRemove(guildID discord.GuildID, userID discord.UserID) error {
|
|
||||||
s.mut.Lock()
|
|
||||||
defer s.mut.Unlock()
|
|
||||||
|
|
||||||
states, ok := s.voiceStates[guildID]
|
|
||||||
if !ok {
|
|
||||||
return ErrStoreNotFound
|
|
||||||
}
|
|
||||||
|
|
||||||
for i, vs := range states {
|
|
||||||
if vs.UserID == userID {
|
|
||||||
states = append(states[:i], states[i+1:]...)
|
|
||||||
s.voiceStates[guildID] = states
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return ErrStoreNotFound
|
|
||||||
}
|
|
|
@ -1,166 +0,0 @@
|
||||||
package state
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
|
|
||||||
"github.com/diamondburned/arikawa/v2/discord"
|
|
||||||
)
|
|
||||||
|
|
||||||
// NoopStore could be embedded by other structs for partial state
|
|
||||||
// implementation. All Getters will return ErrNotImplemented, and all Setters
|
|
||||||
// will return no error.
|
|
||||||
type NoopStore struct{}
|
|
||||||
|
|
||||||
var _ Store = (*NoopStore)(nil)
|
|
||||||
|
|
||||||
var ErrNotImplemented = errors.New("state is not implemented")
|
|
||||||
|
|
||||||
func (NoopStore) Reset() error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (NoopStore) Me() (*discord.User, error) {
|
|
||||||
return nil, ErrNotImplemented
|
|
||||||
}
|
|
||||||
|
|
||||||
func (NoopStore) MyselfSet(discord.User) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (NoopStore) Channel(discord.ChannelID) (*discord.Channel, error) {
|
|
||||||
return nil, ErrNotImplemented
|
|
||||||
}
|
|
||||||
|
|
||||||
func (NoopStore) Channels(discord.GuildID) ([]discord.Channel, error) {
|
|
||||||
return nil, ErrNotImplemented
|
|
||||||
}
|
|
||||||
|
|
||||||
func (NoopStore) CreatePrivateChannel(discord.UserID) (*discord.Channel, error) {
|
|
||||||
return nil, ErrNotImplemented
|
|
||||||
}
|
|
||||||
|
|
||||||
func (NoopStore) PrivateChannels() ([]discord.Channel, error) {
|
|
||||||
return nil, ErrNotImplemented
|
|
||||||
}
|
|
||||||
|
|
||||||
func (NoopStore) ChannelSet(discord.Channel) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (NoopStore) ChannelRemove(discord.Channel) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (NoopStore) Emoji(discord.GuildID, discord.EmojiID) (*discord.Emoji, error) {
|
|
||||||
return nil, ErrNotImplemented
|
|
||||||
}
|
|
||||||
|
|
||||||
func (NoopStore) Emojis(discord.GuildID) ([]discord.Emoji, error) {
|
|
||||||
return nil, ErrNotImplemented
|
|
||||||
}
|
|
||||||
|
|
||||||
func (NoopStore) EmojiSet(discord.GuildID, []discord.Emoji) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (NoopStore) Guild(discord.GuildID) (*discord.Guild, error) {
|
|
||||||
return nil, ErrNotImplemented
|
|
||||||
}
|
|
||||||
|
|
||||||
func (NoopStore) Guilds() ([]discord.Guild, error) {
|
|
||||||
return nil, ErrNotImplemented
|
|
||||||
}
|
|
||||||
|
|
||||||
func (NoopStore) GuildSet(discord.Guild) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (NoopStore) GuildRemove(discord.GuildID) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (NoopStore) Member(discord.GuildID, discord.UserID) (*discord.Member, error) {
|
|
||||||
return nil, ErrNotImplemented
|
|
||||||
}
|
|
||||||
|
|
||||||
func (NoopStore) Members(discord.GuildID) ([]discord.Member, error) {
|
|
||||||
return nil, ErrNotImplemented
|
|
||||||
}
|
|
||||||
|
|
||||||
func (NoopStore) MemberSet(discord.GuildID, discord.Member) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (NoopStore) MemberRemove(discord.GuildID, discord.UserID) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (NoopStore) Message(discord.ChannelID, discord.MessageID) (*discord.Message, error) {
|
|
||||||
return nil, ErrNotImplemented
|
|
||||||
}
|
|
||||||
|
|
||||||
func (NoopStore) Messages(discord.ChannelID) ([]discord.Message, error) {
|
|
||||||
return nil, ErrNotImplemented
|
|
||||||
}
|
|
||||||
|
|
||||||
// MaxMessages will always return 100 messages, so the API can fetch that
|
|
||||||
// many.
|
|
||||||
func (NoopStore) MaxMessages() int {
|
|
||||||
return 100
|
|
||||||
}
|
|
||||||
|
|
||||||
func (NoopStore) MessageSet(discord.Message) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (NoopStore) MessageRemove(discord.ChannelID, discord.MessageID) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (NoopStore) Presence(discord.GuildID, discord.UserID) (*discord.Presence, error) {
|
|
||||||
return nil, ErrNotImplemented
|
|
||||||
}
|
|
||||||
|
|
||||||
func (NoopStore) Presences(discord.GuildID) ([]discord.Presence, error) {
|
|
||||||
return nil, ErrNotImplemented
|
|
||||||
}
|
|
||||||
|
|
||||||
func (NoopStore) PresenceSet(discord.GuildID, discord.Presence) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (NoopStore) PresenceRemove(discord.GuildID, discord.UserID) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (NoopStore) Role(discord.GuildID, discord.RoleID) (*discord.Role, error) {
|
|
||||||
return nil, ErrNotImplemented
|
|
||||||
}
|
|
||||||
|
|
||||||
func (NoopStore) Roles(discord.GuildID) ([]discord.Role, error) {
|
|
||||||
return nil, ErrNotImplemented
|
|
||||||
}
|
|
||||||
|
|
||||||
func (NoopStore) RoleSet(discord.GuildID, discord.Role) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (NoopStore) RoleRemove(discord.GuildID, discord.RoleID) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (NoopStore) VoiceState(discord.GuildID, discord.UserID) (*discord.VoiceState, error) {
|
|
||||||
return nil, ErrNotImplemented
|
|
||||||
}
|
|
||||||
|
|
||||||
func (NoopStore) VoiceStates(discord.GuildID) ([]discord.VoiceState, error) {
|
|
||||||
return nil, ErrNotImplemented
|
|
||||||
}
|
|
||||||
|
|
||||||
func (NoopStore) VoiceStateSet(discord.GuildID, discord.VoiceState) error {
|
|
||||||
return ErrNotImplemented
|
|
||||||
}
|
|
||||||
|
|
||||||
func (NoopStore) VoiceStateRemove(discord.GuildID, discord.UserID) error {
|
|
||||||
return ErrNotImplemented
|
|
||||||
}
|
|
Loading…
Reference in a new issue