mirror of
https://github.com/diamondburned/arikawa.git
synced 2024-12-10 23:45:42 +00:00
703 lines
13 KiB
Go
703 lines
13 KiB
Go
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
|
|
}
|