2020-07-13 00:53:33 +03:00
package chat
import (
2021-07-20 05:22:29 +03:00
"fmt"
2020-12-30 00:35:33 +03:00
"strings"
2020-07-13 00:53:33 +03:00
"time"
2021-07-20 05:22:29 +03:00
"github.com/owncast/owncast/core/chat/events"
2020-10-26 18:55:31 +03:00
"github.com/owncast/owncast/core/data"
2021-07-20 05:22:29 +03:00
"github.com/owncast/owncast/core/user"
2020-10-05 20:07:09 +03:00
"github.com/owncast/owncast/models"
2020-07-13 00:53:33 +03:00
log "github.com/sirupsen/logrus"
)
2021-07-20 05:22:29 +03:00
var _datastore * data . Datastore
const (
maxBacklogHours = 5 // Keep backlog max hours worth of messages
maxBacklogNumber = 50 // Return max number of messages in history request
)
2020-07-13 00:53:33 +03:00
2020-07-16 03:20:47 +03:00
func setupPersistence ( ) {
2021-07-20 05:22:29 +03:00
_datastore = data . GetDatastore ( )
2021-07-23 09:28:01 +03:00
data . CreateMessagesTable ( _datastore . DB )
2022-03-07 07:34:49 +03:00
data . CreateBanIPTable ( _datastore . DB )
2021-07-20 05:22:29 +03:00
chatDataPruner := time . NewTicker ( 5 * time . Minute )
go func ( ) {
runPruner ( )
for range chatDataPruner . C {
runPruner ( )
}
} ( )
2020-07-13 00:53:33 +03:00
}
2021-09-12 10:18:15 +03:00
// SaveUserMessage will save a single chat event to the messages database.
2021-07-20 05:22:29 +03:00
func SaveUserMessage ( event events . UserMessageEvent ) {
2022-01-13 00:53:10 +03:00
saveEvent ( event . ID , & event . User . ID , event . Body , event . Type , event . HiddenAt , event . Timestamp , nil , nil , nil , nil )
2021-07-20 05:22:29 +03:00
}
2022-01-13 00:53:10 +03:00
func saveFederatedAction ( event events . FediverseEngagementEvent ) {
saveEvent ( event . ID , nil , event . Body , event . Type , nil , event . Timestamp , event . Image , & event . Link , & event . UserAccountName , nil )
}
// nolint: unparam
func saveEvent ( id string , userID * string , body string , eventType string , hidden * time . Time , timestamp time . Time , image * string , link * string , title * string , subtitle * string ) {
2021-07-27 03:31:29 +03:00
defer func ( ) {
_historyCache = nil
} ( )
2021-07-20 05:22:29 +03:00
tx , err := _datastore . DB . Begin ( )
2020-10-17 01:04:31 +03:00
if err != nil {
2021-07-20 05:22:29 +03:00
log . Errorln ( "error saving" , eventType , err )
return
2020-10-17 01:04:31 +03:00
}
2020-11-15 05:39:53 +03:00
2021-07-20 05:22:29 +03:00
defer tx . Rollback ( ) // nolint
2022-01-13 00:53:10 +03:00
stmt , err := tx . Prepare ( "INSERT INTO messages(id, user_id, body, eventType, hidden_at, timestamp, image, link, title, subtitle) values(?, ?, ?, ?, ?, ?, ?, ?, ?, ?)" )
2020-07-13 00:53:33 +03:00
if err != nil {
2021-07-20 05:22:29 +03:00
log . Errorln ( "error saving" , eventType , err )
return
2020-07-13 00:53:33 +03:00
}
2021-07-20 05:22:29 +03:00
2020-11-15 05:39:53 +03:00
defer stmt . Close ( )
2022-01-13 00:53:10 +03:00
if _ , err = stmt . Exec ( id , userID , body , eventType , hidden , timestamp , image , link , title , subtitle ) ; err != nil {
2021-07-20 05:22:29 +03:00
log . Errorln ( "error saving" , eventType , err )
return
2020-07-13 00:53:33 +03:00
}
2021-07-20 05:22:29 +03:00
if err = tx . Commit ( ) ; err != nil {
log . Errorln ( "error saving" , eventType , err )
return
2020-11-15 05:39:53 +03:00
}
2020-07-13 00:53:33 +03:00
}
2022-01-13 00:53:10 +03:00
func makeUserMessageEventFromRowData ( row rowData ) events . UserMessageEvent {
scopes := ""
if row . userScopes != nil {
scopes = * row . userScopes
}
previousUsernames := [ ] string { }
if row . previousUsernames != nil {
previousUsernames = strings . Split ( * row . previousUsernames , "," )
}
displayName := ""
if row . userDisplayName != nil {
displayName = * row . userDisplayName
}
displayColor := 0
if row . userDisplayColor != nil {
displayColor = * row . userDisplayColor
}
createdAt := time . Time { }
if row . userCreatedAt != nil {
createdAt = * row . userCreatedAt
}
2022-03-07 07:09:55 +03:00
isBot := ( row . userType != nil && * row . userType == "API" )
scopeSlice := strings . Split ( scopes , "," )
2022-01-13 00:53:10 +03:00
u := user . User {
2022-04-22 00:55:26 +03:00
ID : * row . userID ,
DisplayName : displayName ,
DisplayColor : displayColor ,
CreatedAt : createdAt ,
DisabledAt : row . userDisabledAt ,
NameChangedAt : row . userNameChangedAt ,
PreviousNames : previousUsernames ,
AuthenticatedAt : row . userAuthenticatedAt ,
Authenticated : row . userAuthenticatedAt != nil ,
Scopes : scopeSlice ,
IsBot : isBot ,
2022-01-13 00:53:10 +03:00
}
message := events . UserMessageEvent {
Event : events . Event {
Type : row . eventType ,
ID : row . id ,
Timestamp : row . timestamp ,
} ,
UserEvent : events . UserEvent {
User : & u ,
HiddenAt : row . hiddenAt ,
} ,
MessageEvent : events . MessageEvent {
Body : row . body ,
RawBody : row . body ,
} ,
}
return message
}
func makeSystemMessageChatEventFromRowData ( row rowData ) events . SystemMessageEvent {
message := events . SystemMessageEvent {
Event : events . Event {
Type : row . eventType ,
ID : row . id ,
Timestamp : row . timestamp ,
} ,
MessageEvent : events . MessageEvent {
Body : row . body ,
RawBody : row . body ,
} ,
}
return message
}
func makeActionMessageChatEventFromRowData ( row rowData ) events . ActionEvent {
message := events . ActionEvent {
Event : events . Event {
Type : row . eventType ,
ID : row . id ,
Timestamp : row . timestamp ,
} ,
MessageEvent : events . MessageEvent {
Body : row . body ,
RawBody : row . body ,
} ,
}
return message
}
func makeFederatedActionChatEventFromRowData ( row rowData ) events . FediverseEngagementEvent {
message := events . FediverseEngagementEvent {
Event : events . Event {
Type : row . eventType ,
ID : row . id ,
Timestamp : row . timestamp ,
} ,
MessageEvent : events . MessageEvent {
Body : row . body ,
RawBody : row . body ,
} ,
Image : row . image ,
Link : * row . link ,
UserAccountName : * row . title ,
}
return message
}
type rowData struct {
id string
userID * string
body string
eventType models . EventType
hiddenAt * time . Time
timestamp time . Time
title * string
subtitle * string
image * string
link * string
2022-04-22 00:55:26 +03:00
userDisplayName * string
userDisplayColor * int
userCreatedAt * time . Time
userDisabledAt * time . Time
previousUsernames * string
userNameChangedAt * time . Time
userAuthenticatedAt * time . Time
userScopes * string
userType * string
2022-01-13 00:53:10 +03:00
}
func getChat ( query string ) [ ] interface { } {
history := make ( [ ] interface { } , 0 )
2021-07-20 05:22:29 +03:00
rows , err := _datastore . DB . Query ( query )
2021-11-03 19:43:47 +03:00
if err != nil || rows . Err ( ) != nil {
2021-07-20 05:22:29 +03:00
log . Errorln ( "error fetching chat history" , err )
return history
2020-07-13 00:53:33 +03:00
}
defer rows . Close ( )
for rows . Next ( ) {
2022-01-13 00:53:10 +03:00
row := rowData { }
2021-07-20 05:22:29 +03:00
// Convert a database row into a chat event
2022-01-13 00:53:10 +03:00
if err = rows . Scan (
& row . id ,
& row . userID ,
& row . body ,
& row . title ,
& row . subtitle ,
& row . image ,
& row . link ,
& row . eventType ,
& row . hiddenAt ,
& row . timestamp ,
& row . userDisplayName ,
& row . userDisplayColor ,
& row . userCreatedAt ,
& row . userDisabledAt ,
& row . previousUsernames ,
& row . userNameChangedAt ,
2022-04-22 00:55:26 +03:00
& row . userAuthenticatedAt ,
2022-01-13 00:53:10 +03:00
& row . userScopes ,
2022-03-07 07:09:55 +03:00
& row . userType ,
2022-01-13 00:53:10 +03:00
) ; err != nil {
2022-04-22 00:55:26 +03:00
log . Errorln ( err )
2021-07-20 05:22:29 +03:00
log . Errorln ( "There is a problem converting query to chat objects. Please report this:" , query )
2020-10-16 03:57:32 +03:00
break
2020-07-13 00:53:33 +03:00
}
2022-01-13 00:53:10 +03:00
var message interface { }
switch row . eventType {
case events . MessageSent :
message = makeUserMessageEventFromRowData ( row )
case events . SystemMessageSent :
message = makeSystemMessageChatEventFromRowData ( row )
case events . ChatActionSent :
message = makeActionMessageChatEventFromRowData ( row )
case events . FediverseEngagementFollow :
message = makeFederatedActionChatEventFromRowData ( row )
case events . FediverseEngagementLike :
message = makeFederatedActionChatEventFromRowData ( row )
case events . FediverseEngagementRepost :
message = makeFederatedActionChatEventFromRowData ( row )
2021-07-20 05:22:29 +03:00
}
history = append ( history , message )
2020-11-15 05:39:53 +03:00
}
2020-07-13 00:53:33 +03:00
return history
}
2020-12-30 00:35:33 +03:00
2022-01-13 00:53:10 +03:00
var _historyCache * [ ] interface { }
2021-07-27 03:31:29 +03:00
2021-09-12 10:18:15 +03:00
// GetChatModerationHistory will return all the chat messages suitable for moderation purposes.
2022-01-13 00:53:10 +03:00
func GetChatModerationHistory ( ) [ ] interface { } {
2021-07-27 03:31:29 +03:00
if _historyCache != nil {
return * _historyCache
}
2021-07-20 05:22:29 +03:00
// Get all messages regardless of visibility
2022-04-22 00:55:26 +03:00
query := "SELECT messages.id, user_id, body, title, subtitle, image, link, eventType, hidden_at, timestamp, display_name, display_color, created_at, disabled_at, previous_names, namechanged_at, authenticated_at, scopes, type FROM messages INNER JOIN users ON messages.user_id = users.id ORDER BY timestamp DESC"
2021-07-27 03:31:29 +03:00
result := getChat ( query )
_historyCache = & result
return result
2021-02-19 10:05:52 +03:00
}
2021-09-12 10:18:15 +03:00
// GetChatHistory will return all the chat messages suitable for returning as user-facing chat history.
2022-01-13 00:53:10 +03:00
func GetChatHistory ( ) [ ] interface { } {
2021-07-20 05:22:29 +03:00
// Get all visible messages
2022-04-22 00:55:26 +03:00
query := fmt . Sprintf ( "SELECT messages.id,messages.user_id, messages.body, messages.title, messages.subtitle, messages.image, messages.link, messages.eventType, messages.hidden_at, messages.timestamp, users.display_name, users.display_color, users.created_at, users.disabled_at, users.previous_names, users.namechanged_at, users.authenticated_at, users.scopes, users.type FROM messages LEFT JOIN users ON messages.user_id = users.id WHERE hidden_at IS NULL AND disabled_at IS NULL ORDER BY timestamp DESC LIMIT %d" , maxBacklogNumber )
2021-07-25 19:54:24 +03:00
m := getChat ( query )
// Invert order of messages
for i , j := 0 , len ( m ) - 1 ; i < j ; i , j = i + 1 , j - 1 {
m [ i ] , m [ j ] = m [ j ] , m [ i ]
}
return m
2021-02-19 10:05:52 +03:00
}
2021-09-12 10:18:15 +03:00
// SetMessageVisibilityForUserID will bulk change the visibility of messages for a user
2021-07-20 05:22:29 +03:00
// and then send out visibility changed events to chat clients.
2021-09-12 10:18:15 +03:00
func SetMessageVisibilityForUserID ( userID string , visible bool ) error {
2021-07-27 03:31:29 +03:00
defer func ( ) {
_historyCache = nil
} ( )
2022-01-19 02:38:23 +03:00
// Get a list of IDs to send to the connected clients to hide
2021-07-20 05:22:29 +03:00
ids := make ( [ ] string , 0 )
2022-04-22 00:55:26 +03:00
query := fmt . Sprintf ( "SELECT messages.id, user_id, body, title, subtitle, image, link, eventType, hidden_at, timestamp, display_name, display_color, created_at, disabled_at, previous_names, namechanged_at, authenticated, scopes, type FROM messages INNER JOIN users ON messages.user_id = users.id WHERE user_id IS '%s'" , userID )
2021-07-20 05:22:29 +03:00
messages := getChat ( query )
if len ( messages ) == 0 {
return nil
}
for _ , message := range messages {
2022-01-19 02:38:23 +03:00
ids = append ( ids , message . ( events . UserMessageEvent ) . ID )
2021-07-20 05:22:29 +03:00
}
// Tell the clients to hide/show these messages.
return SetMessagesVisibility ( ids , visible )
}
2020-12-30 00:35:33 +03:00
func saveMessageVisibility ( messageIDs [ ] string , visible bool ) error {
2021-07-27 03:31:29 +03:00
defer func ( ) {
_historyCache = nil
} ( )
2021-07-20 05:22:29 +03:00
_datastore . DbLock . Lock ( )
defer _datastore . DbLock . Unlock ( )
tx , err := _datastore . DB . Begin ( )
2020-12-30 00:35:33 +03:00
if err != nil {
2021-07-20 05:22:29 +03:00
return err
2020-12-30 00:35:33 +03:00
}
2022-03-07 07:34:49 +03:00
// nolint:gosec
2021-07-20 05:22:29 +03:00
stmt , err := tx . Prepare ( "UPDATE messages SET hidden_at=? WHERE id IN (?" + strings . Repeat ( ",?" , len ( messageIDs ) - 1 ) + ")" )
2020-12-30 00:35:33 +03:00
if err != nil {
return err
}
defer stmt . Close ( )
2021-07-20 05:22:29 +03:00
var hiddenAt * time . Time
if ! visible {
now := time . Now ( )
hiddenAt = & now
} else {
hiddenAt = nil
}
2020-12-30 00:35:33 +03:00
args := make ( [ ] interface { } , len ( messageIDs ) + 1 )
2021-07-20 05:22:29 +03:00
args [ 0 ] = hiddenAt
2020-12-30 00:35:33 +03:00
for i , id := range messageIDs {
args [ i + 1 ] = id
}
2021-07-20 05:22:29 +03:00
if _ , err = stmt . Exec ( args ... ) ; err != nil {
2020-12-30 00:35:33 +03:00
return err
}
2021-07-20 05:22:29 +03:00
if err = tx . Commit ( ) ; err != nil {
2020-12-30 00:35:33 +03:00
return err
}
return nil
}