chat.go 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227
  1. package chat
  2. import (
  3. "fmt"
  4. "net/http"
  5. "sort"
  6. "github.com/owncast/owncast/models"
  7. "github.com/owncast/owncast/storage/chatrepository"
  8. "github.com/owncast/owncast/storage/configrepository"
  9. "github.com/owncast/owncast/storage/userrepository"
  10. log "github.com/sirupsen/logrus"
  11. )
  12. type Chat struct {
  13. getStatus func() *models.Status
  14. server *Server
  15. configRepository *configrepository.SqlConfigRepository
  16. emojis *emojis
  17. }
  18. func New() *Chat {
  19. return &Chat{
  20. configRepository: configrepository.Get(),
  21. emojis: newEmojis(),
  22. }
  23. }
  24. var temporaryGlobalInstance *Chat
  25. // GetConfig returns the temporary global instance.
  26. // Remove this after dependency injection is implemented.
  27. func Get() *Chat {
  28. if temporaryGlobalInstance == nil {
  29. temporaryGlobalInstance = New()
  30. }
  31. return temporaryGlobalInstance
  32. }
  33. // Start begins the chat server.
  34. func (c *Chat) Start(getStatusFunc func() *models.Status) error {
  35. c.getStatus = getStatusFunc
  36. c.server = NewChat()
  37. go c.server.Run()
  38. log.Traceln("Chat server started with max connection count of", c.server.maxSocketConnectionLimit)
  39. return nil
  40. }
  41. // FindClientByID will return a single connected client by ID.
  42. func (c *Chat) FindClientByID(clientID uint) (*Client, bool) {
  43. client, found := c.server.clients[clientID]
  44. return client, found
  45. }
  46. // GetClients will return all the current chat clients connected.
  47. func (c *Chat) GetClients() []*Client {
  48. clients := []*Client{}
  49. if c.server == nil {
  50. return clients
  51. }
  52. // Convert the keyed map to a slice.
  53. for _, client := range c.server.clients {
  54. clients = append(clients, client)
  55. }
  56. sort.Slice(clients, func(i, j int) bool {
  57. return clients[i].ConnectedAt.Before(clients[j].ConnectedAt)
  58. })
  59. return clients
  60. }
  61. // SendSystemMessage will send a message string as a system message to all clients.
  62. func (c *Chat) SendSystemMessage(text string, ephemeral bool) error {
  63. message := SystemMessageEvent{
  64. MessageEvent: MessageEvent{
  65. Body: text,
  66. },
  67. }
  68. message.SetDefaults()
  69. message.RenderBody()
  70. message.DisplayName = c.configRepository.GetServerName()
  71. if err := c.Broadcast(&message); err != nil {
  72. log.Errorln("error sending system message", err)
  73. }
  74. if !ephemeral {
  75. cr := chatrepository.Get()
  76. cr.SaveEvent(message.ID, nil, message.Body, message.GetMessageType(), nil, message.Timestamp, nil, nil, nil, nil)
  77. }
  78. return nil
  79. }
  80. // SendFediverseAction will send a message indicating some Fediverse engagement took place.
  81. func (c *Chat) SendFediverseAction(eventType string, userAccountName string, image *string, body string, link string) error {
  82. message := FediverseEngagementEvent{
  83. Event: models.Event{
  84. Type: eventType,
  85. },
  86. MessageEvent: MessageEvent{
  87. Body: body,
  88. },
  89. UserAccountName: userAccountName,
  90. Image: image,
  91. Link: link,
  92. }
  93. message.SetDefaults()
  94. message.RenderBody()
  95. if err := c.Broadcast(&message); err != nil {
  96. log.Errorln("error sending system message", err)
  97. return err
  98. }
  99. cr := chatrepository.Get()
  100. cr.SaveFederatedAction(message)
  101. return nil
  102. }
  103. // SendSystemAction will send a system action string as an action event to all clients.
  104. func (c *Chat) SendSystemAction(text string, ephemeral bool) error {
  105. message := ActionEvent{
  106. MessageEvent: MessageEvent{
  107. Body: text,
  108. },
  109. }
  110. message.SetDefaults()
  111. message.RenderBody()
  112. if err := c.Broadcast(&message); err != nil {
  113. log.Errorln("error sending system chat action")
  114. }
  115. if !ephemeral {
  116. cr := chatrepository.Get()
  117. cr.SaveEvent(message.ID, nil, message.Body, message.GetMessageType(), nil, message.Timestamp, nil, nil, nil, nil)
  118. }
  119. return nil
  120. }
  121. // SendAllWelcomeMessage will send the chat message to all connected clients.
  122. func (c *Chat) SendAllWelcomeMessage() {
  123. c.server.sendAllWelcomeMessage()
  124. }
  125. // SendSystemMessageToClient will send a single message to a single connected chat client.
  126. func (c *Chat) SendSystemMessageToClient(clientID uint, text string) {
  127. if client, foundClient := c.FindClientByID(clientID); foundClient {
  128. c.server.sendSystemMessageToClient(client, text)
  129. }
  130. }
  131. // Broadcast will send all connected clients the outbound object provided.
  132. func (c *Chat) Broadcast(event OutboundEvent) error {
  133. return c.server.Broadcast(event.GetBroadcastPayload())
  134. }
  135. // HandleClientConnection handles a single inbound websocket connection.
  136. func (c *Chat) HandleClientConnection(w http.ResponseWriter, r *http.Request) {
  137. c.server.HandleClientConnection(w, r)
  138. }
  139. // DisconnectClients will forcefully disconnect all clients belonging to a user by ID.
  140. func (c *Chat) DisconnectClients(clients []*Client) {
  141. c.server.DisconnectClients(clients)
  142. }
  143. func (c *Chat) GetClientsForUser(userID string) ([]*Client, error) {
  144. return c.server.GetClientsForUser(userID)
  145. }
  146. // SendConnectedClientInfoToUser will find all the connected clients assigned to a user
  147. // and re-send each the connected client info.
  148. func (c *Chat) SendConnectedClientInfoToUser(userID string) error {
  149. clients, err := c.server.GetClientsForUser(userID)
  150. if err != nil {
  151. return err
  152. }
  153. userRepository := userrepository.Get()
  154. // Get an updated reference to the user.
  155. user := userRepository.GetUserByID(userID)
  156. if user == nil {
  157. return fmt.Errorf("user not found")
  158. }
  159. if err != nil {
  160. return err
  161. }
  162. for _, client := range clients {
  163. // Update the client's reference to its user.
  164. client.User = user
  165. // Send the update to the client.
  166. client.sendConnectedClientInfo()
  167. }
  168. return nil
  169. }
  170. // SendActionToUser will send system action text to all connected clients
  171. // assigned to a user ID.
  172. func (c *Chat) SendActionToUser(userID string, text string) error {
  173. clients, err := c.server.GetClientsForUser(userID)
  174. if err != nil {
  175. return err
  176. }
  177. for _, client := range clients {
  178. c.server.sendActionToClient(client, text)
  179. }
  180. return nil
  181. }