Chat refactor + persistent backing chat users (#1163)

* First pass at chat user registration and validation

* Disable chat if the user is disabled/blocked or the server hits max connections

* Handle dropping sockets if chat is disabled

* Fix origin in automated chat test

* Work for updated chat moderation

* Chat message markdown rendering and fix tests

* Put /api/chat behind a chat user access token. Closes #1085

* Reject blocked username changes

* More WIP moderation

* Defer configuring chat until we know if it is enabled. Closes #1135

* chat user blocking. Closes #1096

* Add tests around user access for #1096

* Add external integration chat message API + update integration auth middleware to pass along integration name. Closes #1092

* Delete old chat messages from db as to not hold on to excessive data. Closes #1152

* Add schema migration for messages. Closes #1155

* Commit updated API documentation

* Add chat load test

* Shared db mutex and db optimizations

* Simplify past display name handling

* Use a new test db for each test run

* Wire up the external messages actions + add tests for them

* Move access tokens to be actual users

* Run message pruning at launch + fix comparison

* Do not return API users in disabled users response

* Fix incorrect highlighting. Closes #1160

* Consolidate user table statements

* Set the max process connection limit to 70% of maximum

* Fix wrong old display name being returned in name change event

* Delete the old chat server files

* Wire back up the webhooks

* Remove unused

* Invalidate user cache on changes

* Do not send rendered body as RawBody

* Some cleanup

* Standardize names for external API users to ExternalAPIUser

* Do not log token

* Checkout branch when building admin for testing

* Bundle in dev admin for testing

* Some cleanup

* Cleanup js logs

* Cleanup and standardize event names

* Clean up some logging

* Update API spec. Closes #1133

* Commit updated API documentation

* Change paths to be better named

* Commit updated API documentation

* Update admin bundle

* Fix duplicate event name

* Rename scope var

* Update admin bundle

* Move connected clients controller into admin package

* Fix collecting usernames for autocomplete purposes

* No longer generate username when it is empty

* Sort clients and users by timestamp

* Move file to admin controller package

* Swap, so the comments stay correct

Co-authored-by: Jannik <jannik@outlook.com>

* Use explicit type alias

Co-authored-by: Jannik <jannik@outlook.com>

* Remove commented code.

Co-authored-by: Jannik <jannik@outlook.com>

* Cleanup test

* Remove some extra logging

* Add some clarity

* Update dev instance of admin for testing

* Consolidate lines

Co-authored-by: Jannik <jannik@outlook.com>

* Remove commented unused vars

Co-authored-by: Jannik <jannik@outlook.com>

* Until needed do not return IP address with client list

* Fix typo of wrong var

* Typo led to a bad test. Fix typo and fix test.

* Guard against the socket reconnecting on error if previously set to shutdown

* Do not log access tokens

* Return success message on enable/disable user

* Clean up some inactionable error messages. Sent ban message. Sort banned users.

* fix styling for when chat is completely disabled

* Unused

* guard against nil clients

* Update dev admin bundle

* Do not unhide messages when unblocking user just to be safe. Send removal action from the controller

* Add convinience function for getting active connections for a single user

* Lock db on these mutations

* Cleanup force disconnect using GetClientsForUser and capture client reference explicitly

* No longer re-showing banned user messages for safety. Removing this test.

* Remove no longer needed comment

* Tweaks to forbidden username handling.

- Standardize naming to not use "block" but "forbidden" instead.
- Pass array over the wire instead of string.
- Add API test
- Fix default list incorrectly being appended to custom list.

* Logging cleanup

* Update dev admin bundle

* Add an artificial delay in order to visually see message being hidden when testing

* Remove the user cache as it is a premature optimization

* When connected to chat let the user know their current user details to sync the username in the UI

* On connected send current display name back to client.
- Move name change out of chat component.
- Add additional event type constants.

* Fix broken workflow due to typo

* Troubleshoot workflow

* Bump htm from 3.0.4 to 3.1.0 in /build/javascript (#1181)

* Bump htm from 3.0.4 to 3.1.0 in /build/javascript

Bumps [htm](https://github.com/developit/htm) from 3.0.4 to 3.1.0.
- [Release notes](https://github.com/developit/htm/releases)
- [Commits](https://github.com/developit/htm/compare/3.0.4...3.1.0)

---
updated-dependencies:
- dependency-name: htm
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

* Run npm run build and update libraries

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Gabe Kangas <gabek@real-ity.com>

* Commit updated Javascript packages

* Re-send current user info when a rejected name change takes place

* All socket writes should be through the send chan and not directly

* Seed the random generator

* Add keys and indexes to users table

* a util to generate consistent emoji markup

* console clean up

* mod tidy

* Commit updated API documentation

* Handle the max payload size of a socket message.
- Only close socket if x2 greater than the max size.
- Send the user a message if a message is too large.
- Surface the max size in bytes in the config.

* Update admin bundle

* Force all events to be sent in their own socket message and do not concatinate in a single message

* Update chat embed to register for access token

* Use a different access token for embed chat

* Update the chat message bubble background color to be bolder

* add base tag to open links in new window, closes #1220

* Support text input of :emoji: in chat (#1190)

* Initial implementation of emoji injection

* fix bookkeeping with multiple emoji

* make the emoji lookup case-insensitive

* try another solution for Caretposition

* add title to emojis

minor refactoring

* bind moji injection to InputKeyUp

* simplify the code

replace all found emojis

* inject emoji if the modifer is released earlier

* more efficient emoji tag search

* use json emoji.emoji as url

* use createEmojiMarkup()

* move emojify() to chat.js

* emojify on paste

* cleanup emoji titles in paste

* update inputText in InputKeyup

* mark emoji titles with 2*zwnj

this way paste cleanup will not interfere with text which include zwnj

* emoji should not change the inputText

* Do not show join messages when chat is offline. Closes #1224
- Show stream starting/ending messages in chat.
- When stream starts show everyone the welcome message.

* Force scrolling chat to bottom after history is populated regardless of scroll position. Closes https://github.com/owncast/owncast/issues/1222

* use maxSocketPayloadSize to calculate total bytes of message payload (#1221)

* utilize maxSocketPayloadSize from config; update chatInput to calculate based on that value instead of text value; remove usage of inputText for counting

* add a buffer to account for entire websocket payload for message char counting; trim nbsp;'s from ends of messages when calculating count

Co-authored-by: Gabe Kangas <gabek@real-ity.com>

Co-authored-by: Owncast <owncast@owncast.online>
Co-authored-by: Jannik <jannik@outlook.com>
Co-authored-by: Ginger Wong <omqmail@gmail.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Meisam <39205857+MFTabriz@users.noreply.github.com>
This commit is contained in:
Gabe Kangas
2021-07-19 19:22:29 -07:00
committed by GitHub
parent e3dc736cf4
commit b6f68628c0
88 changed files with 10691 additions and 2281 deletions

View File

@@ -9,16 +9,24 @@ import (
"net/http"
"github.com/owncast/owncast/controllers"
"github.com/owncast/owncast/core"
"github.com/owncast/owncast/core/chat"
"github.com/owncast/owncast/core/data"
"github.com/owncast/owncast/models"
"github.com/owncast/owncast/core/chat/events"
"github.com/owncast/owncast/core/user"
log "github.com/sirupsen/logrus"
"github.com/teris-io/shortid"
)
// ExternalUpdateMessageVisibility updates an array of message IDs to have the same visiblity.
func ExternalUpdateMessageVisibility(integration user.ExternalAPIUser, w http.ResponseWriter, r *http.Request) {
UpdateMessageVisibility(w, r)
}
// UpdateMessageVisibility updates an array of message IDs to have the same visiblity.
func UpdateMessageVisibility(w http.ResponseWriter, r *http.Request) {
type messageVisibilityUpdateRequest struct {
IDArray []string `json:"idArray"`
Visible bool `json:"visible"`
}
if r.Method != controllers.POST {
controllers.WriteSimpleResponse(w, false, r.Method+" not supported")
return
@@ -27,8 +35,7 @@ func UpdateMessageVisibility(w http.ResponseWriter, r *http.Request) {
decoder := json.NewDecoder(r.Body)
var request messageVisibilityUpdateRequest
err := decoder.Decode(&request)
if err != nil {
if err := decoder.Decode(&request); err != nil {
log.Errorln(err)
controllers.WriteSimpleResponse(w, false, "")
return
@@ -42,103 +49,142 @@ func UpdateMessageVisibility(w http.ResponseWriter, r *http.Request) {
controllers.WriteSimpleResponse(w, true, "changed")
}
type messageVisibilityUpdateRequest struct {
IDArray []string `json:"idArray"`
Visible bool `json:"visible"`
func UpdateUserEnabled(w http.ResponseWriter, r *http.Request) {
type blockUserRequest struct {
UserID string `json:"userId"`
Enabled bool `json:"enabled"`
}
if r.Method != controllers.POST {
controllers.WriteSimpleResponse(w, false, r.Method+" not supported")
return
}
decoder := json.NewDecoder(r.Body)
var request blockUserRequest
if err := decoder.Decode(&request); err != nil {
log.Errorln(err)
controllers.WriteSimpleResponse(w, false, "")
return
}
// Disable/enable the user
if err := user.SetEnabled(request.UserID, request.Enabled); err != nil {
log.Errorln("error changing user enabled status", err)
}
// Hide/show the user's chat messages if disabling.
// Leave hidden messages hidden to be safe.
if !request.Enabled {
if err := chat.SetMessageVisibilityForUserId(request.UserID, request.Enabled); err != nil {
log.Errorln("error changing user messages visibility", err)
}
}
// Forcefully disconnect the user from the chat
if !request.Enabled {
chat.DisconnectUser(request.UserID)
disconnectedUser := user.GetUserById(request.UserID)
_ = chat.SendSystemAction(fmt.Sprintf("**%s** has been removed from chat.", disconnectedUser.DisplayName), true)
}
controllers.WriteSimpleResponse(w, true, fmt.Sprintf("%s enabled: %t", request.UserID, request.Enabled))
}
func GetDisabledUsers(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
users := user.GetDisabledUsers()
controllers.WriteResponse(w, users)
}
// GetChatMessages returns all of the chat messages, unfiltered.
func GetChatMessages(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
messages := core.GetModerationChatMessages()
if err := json.NewEncoder(w).Encode(messages); err != nil {
log.Errorln(err)
}
messages := chat.GetChatModerationHistory()
controllers.WriteResponse(w, messages)
}
// SendSystemMessage will send an official "SYSTEM" message to chat on behalf of your server.
func SendSystemMessage(w http.ResponseWriter, r *http.Request) {
func SendSystemMessage(integration user.ExternalAPIUser, w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
var message models.ChatEvent
var message events.SystemMessageEvent
if err := json.NewDecoder(r.Body).Decode(&message); err != nil {
controllers.InternalErrorHandler(w, err)
return
}
message.MessageType = models.SystemMessageSent
message.Author = data.GetServerName()
message.ClientID = "owncast-server"
message.ID = shortid.MustGenerate()
message.Visible = true
message.SetDefaults()
message.RenderBody()
if err := core.SendMessageToChat(message); err != nil {
if err := chat.SendSystemMessage(message.Body, false); err != nil {
controllers.BadRequestHandler(w, err)
return
}
controllers.WriteSimpleResponse(w, true, "sent")
}
// SendUserMessage will send a message to chat on behalf of a user.
func SendUserMessage(w http.ResponseWriter, r *http.Request) {
// SendUserMessage will send a message to chat on behalf of a user. *Depreciated*.
func SendUserMessage(integration user.ExternalAPIUser, w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
controllers.BadRequestHandler(w, errors.New("no longer supported. see /api/integrations/chat/send"))
}
func SendIntegrationChatMessage(integration user.ExternalAPIUser, w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
var message models.ChatEvent
if err := json.NewDecoder(r.Body).Decode(&message); err != nil {
name := integration.DisplayName
if name == "" {
controllers.BadRequestHandler(w, errors.New("unknown integration for provided access token"))
return
}
var event events.UserMessageEvent
if err := json.NewDecoder(r.Body).Decode(&event); err != nil {
controllers.InternalErrorHandler(w, err)
return
}
event.SetDefaults()
event.RenderBody()
event.Type = "CHAT"
if !message.Valid() {
controllers.BadRequestHandler(w, errors.New("invalid chat message; id, author, and body are required"))
if event.Empty() {
controllers.BadRequestHandler(w, errors.New("invalid message"))
return
}
message.MessageType = models.MessageSent
message.ClientID = "external-request"
message.ID = shortid.MustGenerate()
message.Visible = true
event.User = &user.User{
Id: integration.Id,
DisplayName: name,
DisplayColor: integration.DisplayColor,
CreatedAt: integration.CreatedAt,
}
message.SetDefaults()
message.RenderAndSanitizeMessageBody()
if err := core.SendMessageToChat(message); err != nil {
if err := chat.Broadcast(&event); err != nil {
controllers.BadRequestHandler(w, err)
return
}
chat.SaveUserMessage(event)
controllers.WriteSimpleResponse(w, true, "sent")
}
// SendChatAction will send a generic chat action.
func SendChatAction(w http.ResponseWriter, r *http.Request) {
func SendChatAction(integration user.ExternalAPIUser, w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
var message models.ChatEvent
var message events.SystemActionEvent
if err := json.NewDecoder(r.Body).Decode(&message); err != nil {
controllers.InternalErrorHandler(w, err)
return
}
message.MessageType = models.ChatActionSent
message.ClientID = "external-request"
message.ID = shortid.MustGenerate()
message.Visible = true
if message.Author != "" {
message.Body = fmt.Sprintf("%s %s", message.Author, message.Body)
}
message.SetDefaults()
message.RenderAndSanitizeMessageBody()
message.RenderBody()
if err := core.SendMessageToChat(message); err != nil {
if err := chat.SendSystemAction(message.Body, false); err != nil {
controllers.BadRequestHandler(w, err)
return
}

View File

@@ -12,8 +12,9 @@ import (
"strings"
"github.com/owncast/owncast/controllers"
"github.com/owncast/owncast/core"
"github.com/owncast/owncast/core/chat"
"github.com/owncast/owncast/core/data"
"github.com/owncast/owncast/core/user"
"github.com/owncast/owncast/models"
"github.com/owncast/owncast/utils"
log "github.com/sirupsen/logrus"
@@ -71,17 +72,12 @@ func SetStreamTitle(w http.ResponseWriter, r *http.Request) {
controllers.WriteSimpleResponse(w, true, "changed")
}
func ExternalSetStreamTitle(integration user.ExternalAPIUser, w http.ResponseWriter, r *http.Request) {
SetStreamTitle(w, r)
}
func sendSystemChatAction(messageText string, ephemeral bool) {
message := models.ChatEvent{}
message.Body = messageText
message.MessageType = models.ChatActionSent
message.ClientID = "internal-server"
message.Ephemeral = ephemeral
message.SetDefaults()
message.RenderBody()
if err := core.SendMessageToChat(message); err != nil {
if err := chat.SendSystemAction(messageText, ephemeral); err != nil {
log.Errorln(err)
}
}
@@ -576,17 +572,24 @@ func SetCustomStyles(w http.ResponseWriter, r *http.Request) {
controllers.WriteSimpleResponse(w, true, "custom styles updated")
}
// SetUsernameBlocklist will set the list of usernames we do not allow to use.
func SetUsernameBlocklist(w http.ResponseWriter, r *http.Request) {
usernames, success := getValueFromRequest(w, r)
if !success {
controllers.WriteSimpleResponse(w, false, "unable to update chat username blocklist")
// SetForbiddenUsernameList will set the list of usernames we do not allow to use.
func SetForbiddenUsernameList(w http.ResponseWriter, r *http.Request) {
type forbiddenUsernameListRequest struct {
Value []string `json:"value"`
}
decoder := json.NewDecoder(r.Body)
var request forbiddenUsernameListRequest
if err := decoder.Decode(&request); err != nil {
controllers.WriteSimpleResponse(w, false, "unable to update forbidden usernames with provided values")
return
}
data.SetUsernameBlocklist(usernames.Value.(string))
if err := data.SetForbiddenUsernameList(request.Value); err != nil {
controllers.WriteSimpleResponse(w, false, err.Error())
}
controllers.WriteSimpleResponse(w, true, "blocklist updated")
controllers.WriteSimpleResponse(w, true, "forbidden username list updated")
}
func requirePOST(w http.ResponseWriter, r *http.Request) bool {

View File

@@ -0,0 +1,25 @@
package admin
import (
"encoding/json"
"net/http"
"github.com/owncast/owncast/controllers"
"github.com/owncast/owncast/core/chat"
"github.com/owncast/owncast/core/user"
)
// GetConnectedClients returns currently connected clients.
func GetConnectedClients(w http.ResponseWriter, r *http.Request) {
clients := chat.GetClients()
w.Header().Set("Content-Type", "application/json")
if err := json.NewEncoder(w).Encode(clients); err != nil {
controllers.InternalErrorHandler(w, err)
}
}
// ExternalGetConnectedClients returns currently connected clients.
func ExternalGetConnectedClients(integration user.ExternalAPIUser, w http.ResponseWriter, r *http.Request) {
GetConnectedClients(w, r)
}

View File

@@ -7,31 +7,30 @@ import (
"time"
"github.com/owncast/owncast/controllers"
"github.com/owncast/owncast/core/data"
"github.com/owncast/owncast/models"
"github.com/owncast/owncast/core/user"
"github.com/owncast/owncast/utils"
)
type deleteTokenRequest struct {
type deleteExternalAPIUserRequest struct {
Token string `json:"token"`
}
type createTokenRequest struct {
type createExternalAPIUserRequest struct {
Name string `json:"name"`
Scopes []string `json:"scopes"`
}
// CreateAccessToken will generate a 3rd party access token.
func CreateAccessToken(w http.ResponseWriter, r *http.Request) {
// CreateExternalAPIUser will generate a 3rd party access token.
func CreateExternalAPIUser(w http.ResponseWriter, r *http.Request) {
decoder := json.NewDecoder(r.Body)
var request createTokenRequest
var request createExternalAPIUserRequest
if err := decoder.Decode(&request); err != nil {
controllers.BadRequestHandler(w, err)
return
}
// Verify all the scopes provided are valid
if !models.HasValidScopes(request.Scopes) {
if !user.HasValidScopes(request.Scopes) {
controllers.BadRequestHandler(w, errors.New("one or more invalid scopes provided"))
return
}
@@ -42,26 +41,29 @@ func CreateAccessToken(w http.ResponseWriter, r *http.Request) {
return
}
if err := data.InsertToken(token, request.Name, request.Scopes); err != nil {
color := utils.GenerateRandomDisplayColor()
if err := user.InsertExternalAPIUser(token, request.Name, color, request.Scopes); err != nil {
controllers.InternalErrorHandler(w, err)
return
}
w.Header().Set("Content-Type", "application/json")
controllers.WriteResponse(w, models.AccessToken{
Token: token,
Name: request.Name,
Scopes: request.Scopes,
Timestamp: time.Now(),
LastUsed: nil,
controllers.WriteResponse(w, user.ExternalAPIUser{
AccessToken: token,
DisplayName: request.Name,
DisplayColor: color,
Scopes: request.Scopes,
CreatedAt: time.Now(),
LastUsedAt: nil,
})
}
// GetAccessTokens will return all 3rd party access tokens.
func GetAccessTokens(w http.ResponseWriter, r *http.Request) {
// GetExternalAPIUsers will return all 3rd party access tokens.
func GetExternalAPIUsers(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
tokens, err := data.GetAccessTokens()
tokens, err := user.GetExternalAPIUser()
if err != nil {
controllers.InternalErrorHandler(w, err)
return
@@ -70,8 +72,8 @@ func GetAccessTokens(w http.ResponseWriter, r *http.Request) {
controllers.WriteResponse(w, tokens)
}
// DeleteAccessToken will return a single 3rd party access token.
func DeleteAccessToken(w http.ResponseWriter, r *http.Request) {
// DeleteExternalAPIUser will return a single 3rd party access token.
func DeleteExternalAPIUser(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
if r.Method != controllers.POST {
@@ -80,7 +82,7 @@ func DeleteAccessToken(w http.ResponseWriter, r *http.Request) {
}
decoder := json.NewDecoder(r.Body)
var request deleteTokenRequest
var request deleteExternalAPIUserRequest
if err := decoder.Decode(&request); err != nil {
controllers.BadRequestHandler(w, err)
return
@@ -91,7 +93,7 @@ func DeleteAccessToken(w http.ResponseWriter, r *http.Request) {
return
}
if err := data.DeleteToken(request.Token); err != nil {
if err := user.DeleteExternalAPIUser(request.Token); err != nil {
controllers.InternalErrorHandler(w, err)
return
}

View File

@@ -15,6 +15,7 @@ import (
// GetServerConfig gets the config details of the server.
func GetServerConfig(w http.ResponseWriter, r *http.Request) {
ffmpeg := utils.ValidatedFfmpegPath(data.GetFfMpegPath())
usernameBlocklist := data.GetForbiddenUsernameList()
var videoQualityVariants = make([]models.StreamOutputVariant, 0)
for _, variant := range data.GetStreamOutputVariants() {
@@ -57,11 +58,11 @@ func GetServerConfig(w http.ResponseWriter, r *http.Request) {
Enabled: data.GetDirectoryEnabled(),
InstanceURL: data.GetServerURL(),
},
S3: data.GetS3Config(),
ExternalActions: data.GetExternalActions(),
SupportedCodecs: transcoder.GetCodecs(ffmpeg),
VideoCodec: data.GetVideoCodec(),
UsernameBlocklist: data.GetUsernameBlocklist(),
S3: data.GetS3Config(),
ExternalActions: data.GetExternalActions(),
SupportedCodecs: transcoder.GetCodecs(ffmpeg),
VideoCodec: data.GetVideoCodec(),
ForbiddenUsernames: usernameBlocklist,
}
w.Header().Set("Content-Type", "application/json")
@@ -71,20 +72,20 @@ func GetServerConfig(w http.ResponseWriter, r *http.Request) {
}
type serverConfigAdminResponse struct {
InstanceDetails webConfigResponse `json:"instanceDetails"`
FFmpegPath string `json:"ffmpegPath"`
StreamKey string `json:"streamKey"`
WebServerPort int `json:"webServerPort"`
WebServerIP string `json:"webServerIP"`
RTMPServerPort int `json:"rtmpServerPort"`
S3 models.S3 `json:"s3"`
VideoSettings videoSettings `json:"videoSettings"`
YP yp `json:"yp"`
ChatDisabled bool `json:"chatDisabled"`
ExternalActions []models.ExternalAction `json:"externalActions"`
SupportedCodecs []string `json:"supportedCodecs"`
VideoCodec string `json:"videoCodec"`
UsernameBlocklist string `json:"usernameBlocklist"`
InstanceDetails webConfigResponse `json:"instanceDetails"`
FFmpegPath string `json:"ffmpegPath"`
StreamKey string `json:"streamKey"`
WebServerPort int `json:"webServerPort"`
WebServerIP string `json:"webServerIP"`
RTMPServerPort int `json:"rtmpServerPort"`
S3 models.S3 `json:"s3"`
VideoSettings videoSettings `json:"videoSettings"`
YP yp `json:"yp"`
ChatDisabled bool `json:"chatDisabled"`
ExternalActions []models.ExternalAction `json:"externalActions"`
SupportedCodecs []string `json:"supportedCodecs"`
VideoCodec string `json:"videoCodec"`
ForbiddenUsernames []string `json:"forbiddenUsernames"`
}
type videoSettings struct {

View File

@@ -4,11 +4,17 @@ import (
"encoding/json"
"net/http"
"github.com/owncast/owncast/core"
"github.com/owncast/owncast/core/chat"
"github.com/owncast/owncast/core/user"
"github.com/owncast/owncast/router/middleware"
log "github.com/sirupsen/logrus"
)
// ExternalGetChatMessages gets all of the chat messages.
func ExternalGetChatMessages(integration user.ExternalAPIUser, w http.ResponseWriter, r *http.Request) {
GetChatEmbed(w, r)
}
// GetChatMessages gets all of the chat messages.
func GetChatMessages(w http.ResponseWriter, r *http.Request) {
middleware.EnableCors(&w)
@@ -16,7 +22,7 @@ func GetChatMessages(w http.ResponseWriter, r *http.Request) {
switch r.Method {
case http.MethodGet:
messages := core.GetAllChatMessages()
messages := chat.GetChatHistory()
if err := json.NewEncoder(w).Encode(messages); err != nil {
log.Errorln(err)
@@ -28,3 +34,43 @@ func GetChatMessages(w http.ResponseWriter, r *http.Request) {
}
}
}
// RegisterAnonymousChatUser will register a new user.
func RegisterAnonymousChatUser(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
if r.Method != POST {
WriteSimpleResponse(w, false, r.Method+" not supported")
return
}
type registerAnonymousUserRequest struct {
DisplayName string `json:"displayName"`
}
type registerAnonymousUserResponse struct {
Id string `json:"id"`
AccessToken string `json:"accessToken"`
DisplayName string `json:"displayName"`
}
decoder := json.NewDecoder(r.Body)
var request registerAnonymousUserRequest
if err := decoder.Decode(&request); err != nil { //nolint
// this is fine. register a new user anyway.
}
newUser, err := user.CreateAnonymousUser(request.DisplayName)
if err != nil {
WriteSimpleResponse(w, false, err.Error())
return
}
response := registerAnonymousUserResponse{
Id: newUser.Id,
AccessToken: newUser.AccessToken,
DisplayName: newUser.DisplayName,
}
WriteResponse(w, response)
}

View File

@@ -12,18 +12,19 @@ import (
)
type webConfigResponse struct {
Name string `json:"name"`
Summary string `json:"summary"`
Logo string `json:"logo"`
Tags []string `json:"tags"`
Version string `json:"version"`
NSFW bool `json:"nsfw"`
ExtraPageContent string `json:"extraPageContent"`
StreamTitle string `json:"streamTitle,omitempty"` // What's going on with the current stream
SocialHandles []models.SocialHandle `json:"socialHandles"`
ChatDisabled bool `json:"chatDisabled"`
ExternalActions []models.ExternalAction `json:"externalActions"`
CustomStyles string `json:"customStyles"`
Name string `json:"name"`
Summary string `json:"summary"`
Logo string `json:"logo"`
Tags []string `json:"tags"`
Version string `json:"version"`
NSFW bool `json:"nsfw"`
ExtraPageContent string `json:"extraPageContent"`
StreamTitle string `json:"streamTitle,omitempty"` // What's going on with the current stream
SocialHandles []models.SocialHandle `json:"socialHandles"`
ChatDisabled bool `json:"chatDisabled"`
ExternalActions []models.ExternalAction `json:"externalActions"`
CustomStyles string `json:"customStyles"`
MaxSocketPayloadSize int `json:"maxSocketPayloadSize"`
}
// GetWebConfig gets the status of the server.
@@ -45,18 +46,19 @@ func GetWebConfig(w http.ResponseWriter, r *http.Request) {
serverSummary = utils.RenderPageContentMarkdown(serverSummary)
configuration := webConfigResponse{
Name: data.GetServerName(),
Summary: serverSummary,
Logo: "/logo",
Tags: data.GetServerMetadataTags(),
Version: config.GetReleaseString(),
NSFW: data.GetNSFW(),
ExtraPageContent: pageContent,
StreamTitle: data.GetStreamTitle(),
SocialHandles: socialHandles,
ChatDisabled: data.GetChatDisabled(),
ExternalActions: data.GetExternalActions(),
CustomStyles: data.GetCustomStyles(),
Name: data.GetServerName(),
Summary: serverSummary,
Logo: "/logo",
Tags: data.GetServerMetadataTags(),
Version: config.GetReleaseString(),
NSFW: data.GetNSFW(),
ExtraPageContent: pageContent,
StreamTitle: data.GetStreamTitle(),
SocialHandles: socialHandles,
ChatDisabled: data.GetChatDisabled(),
ExternalActions: data.GetExternalActions(),
CustomStyles: data.GetCustomStyles(),
MaxSocketPayloadSize: config.MaxSocketPayloadSize,
}
if err := json.NewEncoder(w).Encode(configuration); err != nil {

View File

@@ -1,18 +0,0 @@
package controllers
import (
"encoding/json"
"net/http"
"github.com/owncast/owncast/core"
)
// GetConnectedClients returns currently connected clients.
func GetConnectedClients(w http.ResponseWriter, r *http.Request) {
clients := core.GetChatClients()
w.Header().Set("Content-Type", "application/json")
if err := json.NewEncoder(w).Encode(clients); err != nil {
InternalErrorHandler(w, err)
}
}