BareRTC/pkg/echo_messages.go
Noah Petherbridge 885adda156 Echo Public Channel Messages
Add a feature where recent public channel messages can be echoed back to
newly joining users when they enter the chat room.

* Configure in settings.toml with EchoMessagesOnJoin. 0 = disable storage.
* Messages are stored in RAM and lost on a server reboot.
* A buffer of recent public messages per channel can be kept, e.g. for the
  10 most recent messages.
* The settings can be reloaded with /reconfigure and the message buffers
  will rebalance on the next message sent.
* When a new user logs in, a new "echo" message is sent that contains all
  of the echoed messages on a "messages" list, in one WebSocket packet.
* Echoed messages are put above the ChatServer welcome messages.
* If a message is taken back, it's removed from the echo message buffer.

Other Changes

* Don't broadcast Presence messages within 30 seconds of the server boot, to
  lessen a flood of messages when a lot of users were connected at reboot.
* Change the default "Join messages" setting on front-end to hide them in
  public channels.
* For the admin buttons in ProfileModal, use the AlertModal instead of native
  browser prompts.
2025-02-17 22:08:25 -08:00

98 lines
2.7 KiB
Go

package barertc
import (
"sync"
"time"
"git.kirsle.net/apps/barertc/pkg/config"
"git.kirsle.net/apps/barertc/pkg/log"
"git.kirsle.net/apps/barertc/pkg/messages"
)
// Functionality for storing recent public channel messages and echo them to new joiners.
/*
Echo Messages On Join
This feature stores recent public messages to channels (in memory) to echo them
back to new users when they join the room.
*/
var (
echoMessages = map[string][]messages.Message{} // map channel ID -> messages
echoLock sync.RWMutex
)
// SendEchoedMessages will repeat recent public messages in public channels to the newly
// connecting subscriber as echoed messages.
func (sub *Subscriber) SendEchoedMessages() {
var echoes []messages.Message
// Read lock to collect the messages.
echoLock.RLock()
for _, msgs := range echoMessages {
echoes = append(echoes, msgs...)
}
// Release the lock.
echoLock.RUnlock()
// Send all of these in one Echo message.
sub.SendJSON(messages.Message{
Action: messages.ActionEcho,
Messages: echoes,
})
}
// EchoPushPublicMessage pushes a message into the recent message history of the channel ID.
//
// The buffer of recent messages (the size configured in settings.toml) is echoed to
// a new user when they join the chat so they can catch up.
func (s *Server) EchoPushPublicMessage(sub *Subscriber, channel string, msg messages.Message) {
// Get the channel from settings to see its capacity.
ch, ok := config.Current.GetChannel(channel)
if !ok {
return
}
echoLock.Lock()
defer echoLock.Unlock()
// Initialize the storage for this channel?
if _, ok := echoMessages[channel]; !ok {
echoMessages[channel] = []messages.Message{}
}
// Timestamp it and append this message.
msg.Timestamp = time.Now().Format(time.RFC3339)
echoMessages[channel] = append(echoMessages[channel], msg)
// Trim the history to the configured window size.
if ln := len(echoMessages[channel]); ln > ch.EchoMessagesOnJoin {
echoMessages[channel] = echoMessages[channel][ln-ch.EchoMessagesOnJoin:]
}
}
// EchoTakebackMessage will remove any taken-back message that was cached
// in the echo buffer for new joiners.
func (s *Server) EchoTakebackMessage(msgID int64) {
// Takebacks are relatively uncommon enough, write lock while we read and/or
// maybe remove messages from the echo cache.
echoLock.Lock()
defer echoLock.Unlock()
// Find matching messages in each channel.
for _, ch := range config.Current.PublicChannels {
for i, msg := range echoMessages[ch.ID] {
if msg.MessageID == msgID {
log.Error("EchoTakebackMessage: message ID %d removed from channel %s", msgID, ch.ID)
// Remove this message.
echoMessages[ch.ID] = append(echoMessages[ch.ID][:i], echoMessages[ch.ID][i+1:]...)
}
}
}
}