cchat-discord/internal/discord/state/labels/labels.go

298 lines
5.9 KiB
Go

package labels
import (
"sync"
"github.com/diamondburned/arikawa/v2/discord"
"github.com/diamondburned/arikawa/v2/gateway"
"github.com/diamondburned/cchat"
"github.com/diamondburned/cchat-discord/internal/funcutil"
"github.com/diamondburned/cchat-discord/internal/segments/mention"
"github.com/diamondburned/ningen/v2"
)
// Repository is a repository containing LabelContainers. It watches for update
// events from the gateway.
//
// If a labeler that is already registered were to be added again, then the
// adder function will do nothing and will return a callback that does nothing.
type Repository struct {
state *ningen.State
detach func()
stopped bool
mutex sync.Mutex
stores labelContainers
}
// NewRepository creates a new repository.
func NewRepository(state *ningen.State) *Repository {
r := Repository{
state: state,
stores: newLabelContainers(),
}
r.detach = funcutil.JoinCancels(
state.AddHandler(r.onGuildUpdate),
state.AddHandler(r.onMemberUpdate),
state.AddHandler(r.onMemberRemove),
state.AddHandler(r.onChannelUpdate),
state.AddHandler(r.onChannelDelete),
state.AddHandler(r.onGuildMembersChunk),
// TODO: *gateway.GuildMemberListUpdate
)
return &r
}
func (r *Repository) onGuildUpdate(ev *gateway.GuildUpdateEvent) {
r.mutex.Lock()
defer r.mutex.Unlock()
if r.stopped {
return
}
guild, ok := r.stores.guilds[ev.ID]
if !ok {
return
}
rich := mention.NewGuildText(r.state, ev.ID)
for labeler := range guild.guild {
labeler.SetLabel(rich)
}
}
// AddGuildLabel adds a label to display the given guild ID. Refer to Repository
// for more documentation.
func (r *Repository) AddGuildLabel(guildID discord.GuildID, l cchat.LabelContainer) func() {
l.SetLabel(mention.NewGuildText(r.state, guildID))
r.mutex.Lock()
defer r.mutex.Unlock()
guild, _ := r.stores.guilds[guildID]
guild.guild.Add(l)
r.stores.guilds[guildID] = guild
return func() {
r.mutex.Lock()
defer r.mutex.Unlock()
guild, _ := r.stores.guilds[guildID]
guild.guild.Remove(l)
if guild.IsEmpty() {
delete(r.stores.guilds, guildID)
return
}
r.stores.guilds[guildID] = guild
}
}
func (r *Repository) onChannelDelete(ev *gateway.ChannelDeleteEvent) {
// Not sure what to do.
}
func (r *Repository) onChannelUpdate(ev *gateway.ChannelUpdateEvent) {
r.mutex.Lock()
defer r.mutex.Unlock()
if r.stopped {
return
}
channel, ok := r.stores.channels[ev.ID]
if !ok {
return
}
rich := mention.NewChannelText(r.state, ev.ID)
for labeler := range channel {
labeler.SetLabel(rich)
}
}
// AddChannelLabel adds a label to display the given channel live. Refer to
// Repository for more documentation.
func (r *Repository) AddChannelLabel(chID discord.ChannelID, l cchat.LabelContainer) func() {
l.SetLabel(mention.NewChannelText(r.state, chID))
r.mutex.Lock()
defer r.mutex.Unlock()
llist := r.stores.channels[chID]
llist.Add(l)
r.stores.channels[chID] = llist
return func() {
r.mutex.Lock()
defer r.mutex.Unlock()
llist := r.stores.channels[chID]
llist.Remove(l)
if len(llist) == 0 {
delete(r.stores.channels, chID)
return
}
r.stores.channels[chID] = llist
}
}
func (r *Repository) onMemberRemove(ev *gateway.GuildMemberRemoveEvent) {
// Not sure what to do.
}
func (r *Repository) onMemberUpdate(ev *gateway.GuildMemberUpdateEvent) {
r.mutex.Lock()
defer r.mutex.Unlock()
if r.stopped {
return
}
guild, _ := r.stores.guilds[ev.GuildID]
member, ok := guild.members[ev.User.ID]
if !ok {
return
}
rich := mention.NewMemberText(r.state, ev.GuildID, ev.User.ID)
for labeler := range member {
labeler.SetLabel(rich)
}
}
func (r *Repository) onGuildMembersChunk(chunk *gateway.GuildMembersChunkEvent) {
r.mutex.Lock()
defer r.mutex.Unlock()
if r.stopped {
return
}
guild, ok := r.stores.guilds[chunk.GuildID]
if !ok {
return
}
for _, member := range chunk.Members {
m, ok := guild.members[member.User.ID]
if ok {
rich := mention.NewMemberText(r.state, chunk.GuildID, member.User.ID)
for labeler := range m {
labeler.SetLabel(rich)
}
}
}
}
// AddMemberLabel adds a label to display the given member live. If the given
// guildID is not valid, then AddPresenceLabel will be called. Refer to
// Repository for more documentation.
func (r *Repository) AddMemberLabel(
guildID discord.GuildID, userID discord.UserID, l cchat.LabelContainer) func() {
if !guildID.IsValid() {
return r.AddPresenceLabel(userID, l)
}
l.SetLabel(mention.NewMemberText(r.state, guildID, userID))
r.mutex.Lock()
defer r.mutex.Unlock()
guild, _ := r.stores.guilds[guildID]
llist := guild.members[userID]
llist.Add(l)
if guild.members == nil {
guild.members = make(map[discord.UserID]labelerList, 1)
}
guild.members[userID] = llist
r.stores.guilds[guildID] = guild
return func() {
r.mutex.Lock()
defer r.mutex.Unlock()
guild, _ := r.stores.guilds[guildID]
llist := guild.members[userID]
llist.Remove(l)
if guild.IsEmpty() {
delete(r.stores.guilds, guildID)
return
}
guild.members[userID] = llist
r.stores.guilds[guildID] = guild
}
}
func (r *Repository) onPresenceUpdate(ev *gateway.PresenceUpdateEvent) {
r.mutex.Lock()
defer r.mutex.Unlock()
if r.stopped {
return
}
labels, ok := r.stores.presences[ev.User.ID]
if !ok {
return
}
rich := mention.NewUserText(r.state, ev.User.ID)
for label := range labels {
label.SetLabel(rich)
}
}
func (r *Repository) AddPresenceLabel(uID discord.UserID, l cchat.LabelContainer) func() {
l.SetLabel(mention.NewUserText(r.state, uID))
r.mutex.Lock()
defer r.mutex.Unlock()
llist := r.stores.presences[uID]
llist.Add(l)
r.stores.presences[uID] = llist
return func() {
r.mutex.Lock()
defer r.mutex.Unlock()
llist := r.stores.presences[uID]
llist.Remove(l)
if len(llist) == 0 {
delete(r.stores.presences, uID)
return
}
r.stores.presences[uID] = llist
}
}
// Stop detaches all handlers.
func (r *Repository) Stop() {
r.mutex.Lock()
r.stopped = true
r.mutex.Unlock()
r.detach()
}