0

Rework how hiding messages works. (#1509)

* Rework how hiding messages work. Fixes #1350

* Remove unused function

* Revert to old event name to support previously saved webhooks
This commit is contained in:
Gabe Kangas 2021-11-02 18:00:15 -07:00 committed by GitHub
parent 2278fec70a
commit b43c5e674e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 93 additions and 86 deletions

View File

@ -10,8 +10,8 @@ const (
UserJoined EventType = "USER_JOINED"
// UserNameChanged is the event sent when a chat username change takes place.
UserNameChanged EventType = "NAME_CHANGE"
// VisibiltyToggled is the event sent when a chat message's visibility changes.
VisibiltyToggled EventType = "VISIBILITY-UPDATE"
// VisibiltyUpdate is the event sent when a chat message's visibility changes.
VisibiltyUpdate EventType = "VISIBILITY-UPDATE"
// PING is a ping message.
PING EventType = "PING"
// PONG is a pong message.

View File

@ -0,0 +1,21 @@
package events
// SetMessageVisibilityEvent is the event fired when one or more message
// visibilities are changed.
type SetMessageVisibilityEvent struct {
Event
UserMessageEvent
MessageIDs []string
Visible bool
}
// GetBroadcastPayload will return the object to send to all chat users.
func (e *SetMessageVisibilityEvent) GetBroadcastPayload() EventPayload {
return EventPayload{
"type": VisibiltyUpdate,
"id": e.ID,
"timestamp": e.Timestamp,
"ids": e.MessageIDs,
"visible": e.Visible,
}
}

View File

@ -1,6 +1,8 @@
package chat
import (
"errors"
"github.com/owncast/owncast/core/chat/events"
"github.com/owncast/owncast/core/webhooks"
log "github.com/sirupsen/logrus"
@ -14,23 +16,26 @@ func SetMessagesVisibility(messageIDs []string, visibility bool) error {
return err
}
// Send an update event to all clients for each message.
// Note: Our client expects a single message at a time, so we can't just
// send an array of messages in a single update.
for _, id := range messageIDs {
message, err := getMessageByID(id)
if err != nil {
log.Errorln(err)
continue
}
payload := message.GetBroadcastPayload()
payload["type"] = events.VisibiltyToggled
if err := _server.Broadcast(payload); err != nil {
log.Debugln(err)
}
go webhooks.SendChatEvent(message)
// Send an event letting the chat clients know to hide or show
// the messages.
event := events.SetMessageVisibilityEvent{
MessageIDs: messageIDs,
Visible: visibility,
}
event.Event.SetDefaults()
payload := event.GetBroadcastPayload()
if err := _server.Broadcast(payload); err != nil {
return errors.New("error broadcasting message visibility payload " + err.Error())
}
// Send webhook
wh := webhooks.WebhookEvent{
EventData: event,
Type: event.GetMessageType(),
}
webhooks.SendEventToWebhooks(wh)
return nil
}

View File

@ -161,7 +161,7 @@ func GetChatModerationHistory() []events.UserMessageEvent {
}
// Get all messages regardless of visibility
var query = "SELECT messages.id, user_id, body, eventType, hidden_at, timestamp, display_name, display_color, created_at, disabled_at, previous_names, namechanged_at FROM messages INNER JOIN users ON messages.user_id = users.id ORDER BY timestamp DESC"
query := "SELECT messages.id, user_id, body, eventType, hidden_at, timestamp, display_name, display_color, created_at, disabled_at, previous_names, namechanged_at FROM messages INNER JOIN users ON messages.user_id = users.id ORDER BY timestamp DESC"
result := getChat(query)
_historyCache = &result
@ -172,7 +172,7 @@ func GetChatModerationHistory() []events.UserMessageEvent {
// GetChatHistory will return all the chat messages suitable for returning as user-facing chat history.
func GetChatHistory() []events.UserMessageEvent {
// Get all visible messages
var query = fmt.Sprintf("SELECT messages.id, user_id, body, eventType, hidden_at, timestamp, display_name, display_color, created_at, disabled_at, previous_names, namechanged_at FROM messages, users WHERE messages.user_id = users.id AND hidden_at IS NULL AND disabled_at IS NULL ORDER BY timestamp DESC LIMIT %d", maxBacklogNumber)
query := fmt.Sprintf("SELECT messages.id, user_id, body, eventType, hidden_at, timestamp, display_name, display_color, created_at, disabled_at, previous_names, namechanged_at FROM messages, users WHERE messages.user_id = users.id AND hidden_at IS NULL AND disabled_at IS NULL ORDER BY timestamp DESC LIMIT %d", maxBacklogNumber)
m := getChat(query)
// Invert order of messages
@ -221,7 +221,6 @@ func saveMessageVisibility(messageIDs []string, visible bool) error {
}
stmt, err := tx.Prepare("UPDATE messages SET hidden_at=? WHERE id IN (?" + strings.Repeat(",?", len(messageIDs)-1) + ")")
if err != nil {
return err
}
@ -252,41 +251,6 @@ func saveMessageVisibility(messageIDs []string, visible bool) error {
return nil
}
func getMessageByID(messageID string) (*events.UserMessageEvent, error) {
var query = "SELECT * FROM messages WHERE id = ?"
row := _datastore.DB.QueryRow(query, messageID)
var id string
var userID string
var body string
var eventType models.EventType
var hiddenAt *time.Time
var timestamp time.Time
err := row.Scan(&id, &userID, &body, &eventType, &hiddenAt, &timestamp)
if err != nil {
log.Errorln(err)
return nil, err
}
user := user.GetUserByID(userID)
return &events.UserMessageEvent{
events.Event{
Type: eventType,
ID: id,
Timestamp: timestamp,
},
events.UserEvent{
User: user,
HiddenAt: hiddenAt,
},
events.MessageEvent{
Body: body,
},
}, nil
}
// Only keep recent messages so we don't keep more chat data than needed
// for privacy and efficiency reasons.
func runPruner() {

View File

@ -29,6 +29,7 @@ export default class Chat extends Component {
this.receivedFirstMessages = false;
this.receivedMessageUpdate = false;
this.hasFetchedHistory = false;
this.forceRender = false;
this.windowBlurred = false;
this.numMessagesSinceBlur = 0;
@ -69,6 +70,11 @@ export default class Chat extends Component {
const { webSocketConnected, messages, chatUserNames, newMessagesReceived } =
this.state;
if (this.forceRender) {
return true;
}
const {
webSocketConnected: nextSocket,
messages: nextMessages,
@ -185,42 +191,51 @@ export default class Chat extends Component {
(item) => item.id === messageId
);
// If the message already exists and this is an update event
// then update it.
const updatedMessageList = [...curMessages];
// Change the visibility of messages by ID.
if (messageType === 'VISIBILITY-UPDATE') {
const updatedMessageList = [...curMessages];
const idsToUpdate = message.ids;
const visible = message.visible;
updatedMessageList.forEach((item) => {
if (idsToUpdate.includes(item.id)) {
item.visible = visible;
}
this.forceRender = true;
this.setState({
messages: updatedMessageList,
});
});
return;
} else if (existingIndex === -1 && messageVisible) {
const convertedMessage = {
...message,
type: 'CHAT',
};
// if message exists and should now hide, take it out.
if (existingIndex >= 0 && !messageVisible) {
this.setState({
messages: curMessages.filter((item) => item.id !== messageId),
});
} else if (existingIndex === -1 && messageVisible) {
// insert message at timestamp
const insertAtIndex = curMessages.findIndex((item, index) => {
const time = item.timestamp || messageTimestamp;
const nextMessage =
index < curMessages.length - 1 && curMessages[index + 1];
const nextTime = nextMessage.timestamp || messageTimestamp;
const messageTimestampDate = new Date(messageTimestamp);
return (
messageTimestampDate > new Date(time) &&
messageTimestampDate <= new Date(nextTime)
);
});
updatedMessageList.splice(insertAtIndex + 1, 0, convertedMessage);
if (updatedMessageList.length > 300) {
updatedMessageList = updatedMessageList.slice(
Math.max(updatedMessageList.length - 300, 0)
);
}
this.setState({
messages: updatedMessageList,
});
// insert message at timestamp
const insertAtIndex = curMessages.findIndex((item, index) => {
const time = item.timestamp || messageTimestamp;
const nextMessage =
index < curMessages.length - 1 && curMessages[index + 1];
const nextTime = nextMessage.timestamp || messageTimestamp;
const messageTimestampDate = new Date(messageTimestamp);
return (
messageTimestampDate > new Date(time) &&
messageTimestampDate <= new Date(nextTime)
);
});
updatedMessageList.splice(insertAtIndex + 1, 0, convertedMessage);
if (updatedMessageList.length > 300) {
updatedMessageList = updatedMessageList.slice(
Math.max(updatedMessageList.length - 300, 0)
);
}
this.setState({
messages: updatedMessageList,
});
} else if (existingIndex === -1) {
// else if message doesn't exist, add it and extra username
const newState = {
@ -354,6 +369,8 @@ export default class Chat extends Component {
const { username, readonly, chatInputEnabled, inputMaxBytes } = props;
const { messages, chatUserNames, webSocketConnected } = state;
this.forceRender = false;
const messageList = messages
.filter((message) => message.visible !== false)
.map(