diff --git a/build/admin/bundleAdmin.sh b/build/admin/bundleAdmin.sh
index ba01fcefa..3b0ddbb52 100755
--- a/build/admin/bundleAdmin.sh
+++ b/build/admin/bundleAdmin.sh
@@ -18,6 +18,7 @@ trap shutdown INT TERM ABRT EXIT
echo "Cloning owncast admin into $INSTALL_TEMP_DIRECTORY..."
git clone https://github.com/owncast/owncast-admin 2> /dev/null
cd owncast-admin
+git checkout gek/chat-user-refactor
echo "Installing npm modules for the owncast admin..."
npm --silent install 2> /dev/null
diff --git a/config/config.go b/config/config.go
index a0b067386..3a87c56d0 100644
--- a/config/config.go
+++ b/config/config.go
@@ -42,6 +42,13 @@ func GetCommit() string {
return GitCommit
}
+var DefaultForbiddenUsernames = []string{
+ "owncast", "operator", "admin", "system",
+}
+
+// The maximum payload we will allow to to be received via the chat socket.
+const MaxSocketPayloadSize = 2048
+
// GetReleaseString gets the version string.
func GetReleaseString() string {
var versionNumber = VersionNumber
diff --git a/controllers/admin/chat.go b/controllers/admin/chat.go
index f9166ac0e..cf84f118f 100644
--- a/controllers/admin/chat.go
+++ b/controllers/admin/chat.go
@@ -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
}
diff --git a/controllers/admin/config.go b/controllers/admin/config.go
index e926e0ad5..471c22669 100644
--- a/controllers/admin/config.go
+++ b/controllers/admin/config.go
@@ -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 {
diff --git a/controllers/admin/connectedClients.go b/controllers/admin/connectedClients.go
new file mode 100644
index 000000000..410f9e1a4
--- /dev/null
+++ b/controllers/admin/connectedClients.go
@@ -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)
+}
diff --git a/controllers/admin/accessToken.go b/controllers/admin/externalAPIUsers.go
similarity index 56%
rename from controllers/admin/accessToken.go
rename to controllers/admin/externalAPIUsers.go
index 077004273..3e932936b 100644
--- a/controllers/admin/accessToken.go
+++ b/controllers/admin/externalAPIUsers.go
@@ -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
}
diff --git a/controllers/admin/serverConfig.go b/controllers/admin/serverConfig.go
index b7f95a304..bf854064a 100644
--- a/controllers/admin/serverConfig.go
+++ b/controllers/admin/serverConfig.go
@@ -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 {
diff --git a/controllers/chat.go b/controllers/chat.go
index bba843a00..45a11738d 100644
--- a/controllers/chat.go
+++ b/controllers/chat.go
@@ -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)
+}
diff --git a/controllers/config.go b/controllers/config.go
index ea302f24c..4fe29e7b5 100644
--- a/controllers/config.go
+++ b/controllers/config.go
@@ -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 {
diff --git a/controllers/connectedClients.go b/controllers/connectedClients.go
deleted file mode 100644
index 384abd2d6..000000000
--- a/controllers/connectedClients.go
+++ /dev/null
@@ -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)
- }
-}
diff --git a/core/chat/chat.go b/core/chat/chat.go
index 631887d7f..179d602de 100644
--- a/core/chat/chat.go
+++ b/core/chat/chat.go
@@ -2,84 +2,113 @@ package chat
import (
"errors"
- "time"
+ "net/http"
+ "sort"
+ "github.com/owncast/owncast/core/chat/events"
"github.com/owncast/owncast/models"
+ log "github.com/sirupsen/logrus"
)
-// Setup sets up the chat server.
-func Setup(listener models.ChatListener) {
+var getStatus func() models.Status
+
+func Start(getStatusFunc func() models.Status) error {
setupPersistence()
- clients := make(map[string]*Client)
- addCh := make(chan *Client)
- delCh := make(chan *Client)
- sendAllCh := make(chan models.ChatEvent)
- pingCh := make(chan models.PingMessage)
- doneCh := make(chan bool)
- errCh := make(chan error)
+ getStatus = getStatusFunc
+ _server = NewChat()
- _server = &server{
- clients,
- "/entry", //hardcoded due to the UI requiring this and it is not configurable
- listener,
- addCh,
- delCh,
- sendAllCh,
- pingCh,
- doneCh,
- errCh,
- }
-}
+ go _server.Run()
-// Start starts the chat server.
-func Start() error {
- if _server == nil {
- return errors.New("chat server is nil")
- }
+ log.Traceln("Chat server started with max connection count of", _server.maxClientCount)
- ticker := time.NewTicker(30 * time.Second)
- go func() {
- for range ticker.C {
- _server.ping()
- }
- }()
-
- _server.Listen()
-
- return errors.New("chat server failed to start")
-}
-
-// SendMessage sends a message to all.
-func SendMessage(message models.ChatEvent) {
- if _server == nil {
- return
- }
-
- _server.SendToAll(message)
-}
-
-// GetMessages gets all of the messages.
-func GetMessages() []models.ChatEvent {
- if _server == nil {
- return []models.ChatEvent{}
- }
-
- return getChatHistory()
-}
-
-func GetModerationChatMessages() []models.ChatEvent {
- return getChatModerationHistory()
-}
-
-func GetClient(clientID string) *Client {
- l.RLock()
- defer l.RUnlock()
-
- for _, client := range _server.Clients {
- if client.ClientID == clientID {
- return client
- }
- }
return nil
}
+
+// GetClientsForUser will return chat connections that are owned by a specific user.
+func GetClientsForUser(userID string) ([]*ChatClient, error) {
+ clients := map[string][]*ChatClient{}
+
+ for _, client := range _server.clients {
+ clients[client.User.Id] = append(clients[client.User.Id], client)
+ }
+
+ if _, exists := clients[userID]; !exists {
+ return nil, errors.New("no connections for user found")
+ }
+
+ return clients[userID], nil
+}
+
+func GetClients() []*ChatClient {
+ clients := []*ChatClient{}
+
+ // Convert the keyed map to a slice.
+ for _, client := range _server.clients {
+ clients = append(clients, client)
+ }
+
+ sort.Slice(clients, func(i, j int) bool {
+ return clients[i].ConnectedAt.Before(clients[j].ConnectedAt)
+ })
+
+ return clients
+}
+
+func SendSystemMessage(text string, ephemeral bool) error {
+ message := events.SystemMessageEvent{
+ MessageEvent: events.MessageEvent{
+ Body: text,
+ },
+ }
+ message.SetDefaults()
+ message.RenderBody()
+
+ if err := Broadcast(&message); err != nil {
+ log.Errorln("error sending system message", err)
+ }
+
+ if !ephemeral {
+ saveEvent(message.Id, "system", message.Body, message.GetMessageType(), nil, message.Timestamp)
+ }
+
+ return nil
+}
+
+func SendSystemAction(text string, ephemeral bool) error {
+ message := events.ActionEvent{
+ MessageEvent: events.MessageEvent{
+ Body: text,
+ },
+ }
+
+ message.SetDefaults()
+ message.RenderBody()
+
+ if err := Broadcast(&message); err != nil {
+ log.Errorln("error sending system chat action")
+ }
+
+ if !ephemeral {
+ saveEvent(message.Id, "action", message.Body, message.GetMessageType(), nil, message.Timestamp)
+ }
+
+ return nil
+}
+
+func SendAllWelcomeMessage() {
+ _server.sendAllWelcomeMessage()
+}
+
+func Broadcast(event events.OutboundEvent) error {
+ return _server.Broadcast(event.GetBroadcastPayload())
+}
+
+func HandleClientConnection(w http.ResponseWriter, r *http.Request) {
+ _server.HandleClientConnection(w, r)
+}
+
+// DisconnectUser will forcefully disconnect all clients belonging to a user by ID.
+func DisconnectUser(userID string) {
+ _server.DisconnectUser(userID)
+}
diff --git a/core/chat/chatclient.go b/core/chat/chatclient.go
new file mode 100644
index 000000000..69015b007
--- /dev/null
+++ b/core/chat/chatclient.go
@@ -0,0 +1,190 @@
+package chat
+
+import (
+ "bytes"
+ "encoding/json"
+ "time"
+
+ log "github.com/sirupsen/logrus"
+ "golang.org/x/time/rate"
+
+ "github.com/gorilla/websocket"
+ "github.com/owncast/owncast/config"
+ "github.com/owncast/owncast/core/chat/events"
+ "github.com/owncast/owncast/core/user"
+ "github.com/owncast/owncast/geoip"
+)
+
+type ChatClient struct {
+ id uint
+ accessToken string
+ conn *websocket.Conn
+ User *user.User `json:"user"`
+ server *ChatServer
+ ipAddress string `json:"-"`
+ // Buffered channel of outbound messages.
+ send chan []byte
+ rateLimiter *rate.Limiter
+ Geo *geoip.GeoDetails `json:"geo"`
+ MessageCount int `json:"messageCount"`
+ UserAgent string `json:"userAgent"`
+ ConnectedAt time.Time `json:"connectedAt"`
+}
+
+type chatClientEvent struct {
+ data []byte
+ client *ChatClient
+}
+
+const (
+ // Time allowed to write a message to the peer.
+ writeWait = 10 * time.Second
+
+ // Time allowed to read the next pong message from the peer.
+ pongWait = 60 * time.Second
+
+ // Send pings to peer with this period. Must be less than pongWait.
+ pingPeriod = (pongWait * 9) / 10
+
+ // Maximum message size allowed from peer.
+ // Larger messages get thrown away.
+ // Messages > *2 the socket gets closed.
+ maxMessageSize = config.MaxSocketPayloadSize
+)
+
+var upgrader = websocket.Upgrader{
+ ReadBufferSize: 1024,
+ WriteBufferSize: 1024,
+}
+
+var (
+ newline = []byte{'\n'}
+ space = []byte{' '}
+)
+
+func (c *ChatClient) sendConnectedClientInfo() {
+ payload := events.EventPayload{
+ "type": events.ConnectedUserInfo,
+ "user": c.User,
+ }
+
+ c.sendPayload(payload)
+}
+
+func (c *ChatClient) readPump() {
+ c.rateLimiter = rate.NewLimiter(0.6, 5)
+
+ defer func() {
+ c.close()
+ }()
+
+ // If somebody is sending 2x the max message size they're likely a bad actor
+ // and should be disconnected. Below we throw away messages > max size.
+ c.conn.SetReadLimit(maxMessageSize * 2)
+
+ _ = c.conn.SetReadDeadline(time.Now().Add(pongWait))
+ c.conn.SetPongHandler(func(string) error { _ = c.conn.SetReadDeadline(time.Now().Add(pongWait)); return nil })
+ for {
+ _, message, err := c.conn.ReadMessage()
+
+ if err != nil {
+ if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway, websocket.CloseAbnormalClosure) {
+ c.close()
+ }
+ break
+ }
+
+ // Throw away messages greater than max message size.
+ if len(message) > maxMessageSize {
+ c.sendAction("Sorry, that message exceeded the maximum size and can't be delivered.")
+ continue
+ }
+
+ // Guard against floods.
+ if !c.passesRateLimit() {
+ continue
+ }
+
+ message = bytes.TrimSpace(bytes.Replace(message, newline, space, -1))
+ c.handleEvent(message)
+ }
+}
+
+func (c *ChatClient) writePump() {
+ ticker := time.NewTicker(pingPeriod)
+ defer func() {
+ ticker.Stop()
+ c.conn.Close()
+ }()
+
+ for {
+ select {
+ case message, ok := <-c.send:
+ _ = c.conn.SetWriteDeadline(time.Now().Add(writeWait))
+ if !ok {
+ // The server closed the channel.
+ _ = c.conn.WriteMessage(websocket.CloseMessage, []byte{})
+ return
+ }
+
+ w, err := c.conn.NextWriter(websocket.TextMessage)
+ if err != nil {
+ return
+ }
+ if _, err := w.Write(message); err != nil {
+ log.Debugln(err)
+ }
+
+ if err := w.Close(); err != nil {
+ return
+ }
+ case <-ticker.C:
+ _ = c.conn.SetWriteDeadline(time.Now().Add(writeWait))
+ if err := c.conn.WriteMessage(websocket.PingMessage, nil); err != nil {
+ return
+ }
+ }
+ }
+}
+
+func (c *ChatClient) handleEvent(data []byte) {
+ c.server.inbound <- chatClientEvent{data: data, client: c}
+}
+
+func (c *ChatClient) close() {
+ log.Traceln("client closed:", c.User.DisplayName, c.id, c.ipAddress)
+
+ c.conn.Close()
+ c.server.unregister <- c
+}
+
+func (c *ChatClient) passesRateLimit() bool {
+ if !c.rateLimiter.Allow() {
+ log.Debugln("Client", c.id, c.User.DisplayName, "has exceeded the messaging rate limiting thresholds.")
+ return false
+ }
+
+ return true
+}
+
+func (c *ChatClient) sendPayload(payload events.EventPayload) {
+ var data []byte
+ data, err := json.Marshal(payload)
+ if err != nil {
+ log.Errorln(err)
+ return
+ }
+
+ c.send <- data
+}
+
+func (c *ChatClient) sendAction(message string) {
+ clientMessage := events.ActionEvent{
+ MessageEvent: events.MessageEvent{
+ Body: message,
+ },
+ }
+ clientMessage.SetDefaults()
+ clientMessage.RenderBody()
+ c.sendPayload(clientMessage.GetBroadcastPayload())
+}
diff --git a/core/chat/client.go b/core/chat/client.go
deleted file mode 100644
index bb9275ab4..000000000
--- a/core/chat/client.go
+++ /dev/null
@@ -1,241 +0,0 @@
-package chat
-
-import (
- "encoding/json"
- "fmt"
- "io"
- "time"
-
- log "github.com/sirupsen/logrus"
- "golang.org/x/net/websocket"
-
- "github.com/owncast/owncast/geoip"
- "github.com/owncast/owncast/models"
- "github.com/owncast/owncast/utils"
-
- "github.com/teris-io/shortid"
- "golang.org/x/time/rate"
-)
-
-const channelBufSize = 100
-
-//Client represents a chat client.
-type Client struct {
- ConnectedAt time.Time
- MessageCount int
- UserAgent string
- IPAddress string
- Username *string
- ClientID string // How we identify unique viewers when counting viewer counts.
- Geo *geoip.GeoDetails `json:"geo"`
- Ignore bool // If set to true this will not be treated as a viewer
-
- socketID string // How we identify a single websocket client.
- ws *websocket.Conn
- ch chan models.ChatEvent
- pingch chan models.PingMessage
- usernameChangeChannel chan models.NameChangeEvent
- userJoinedChannel chan models.UserJoinedEvent
-
- doneCh chan bool
-
- rateLimiter *rate.Limiter
-}
-
-// NewClient creates a new chat client.
-func NewClient(ws *websocket.Conn) *Client {
- if ws == nil {
- log.Panicln("ws cannot be nil")
- }
-
- var ignoreClient = false
- for _, extraData := range ws.Config().Protocol {
- if extraData == "IGNORE_CLIENT" {
- ignoreClient = true
- }
- }
-
- ch := make(chan models.ChatEvent, channelBufSize)
- doneCh := make(chan bool)
- pingch := make(chan models.PingMessage)
- usernameChangeChannel := make(chan models.NameChangeEvent)
- userJoinedChannel := make(chan models.UserJoinedEvent)
-
- ipAddress := utils.GetIPAddressFromRequest(ws.Request())
- userAgent := ws.Request().UserAgent()
- socketID, _ := shortid.Generate()
- clientID := socketID
-
- rateLimiter := rate.NewLimiter(0.6, 5)
-
- return &Client{time.Now(), 0, userAgent, ipAddress, nil, clientID, nil, ignoreClient, socketID, ws, ch, pingch, usernameChangeChannel, userJoinedChannel, doneCh, rateLimiter}
-}
-
-func (c *Client) write(msg models.ChatEvent) {
- select {
- case c.ch <- msg:
- default:
- _server.removeClient(c)
- _server.err(fmt.Errorf("client %s is disconnected", c.ClientID))
- }
-}
-
-// Listen Write and Read request via channel.
-func (c *Client) listen() {
- go c.listenWrite()
- c.listenRead()
-}
-
-// Listen write request via channel.
-func (c *Client) listenWrite() {
- for {
- select {
- // Send a PING keepalive
- case msg := <-c.pingch:
- if err := websocket.JSON.Send(c.ws, msg); err != nil {
- c.handleClientSocketError(err)
- }
- // send message to the client
- case msg := <-c.ch:
- if err := websocket.JSON.Send(c.ws, msg); err != nil {
- c.handleClientSocketError(err)
- }
- case msg := <-c.usernameChangeChannel:
- if err := websocket.JSON.Send(c.ws, msg); err != nil {
- c.handleClientSocketError(err)
- }
- case msg := <-c.userJoinedChannel:
- if err := websocket.JSON.Send(c.ws, msg); err != nil {
- c.handleClientSocketError(err)
- }
-
- // receive done request
- case <-c.doneCh:
- _server.removeClient(c)
- c.doneCh <- true // for listenRead method
- return
- }
- }
-}
-
-func (c *Client) handleClientSocketError(err error) {
- _server.removeClient(c)
-}
-
-func (c *Client) passesRateLimit() bool {
- if !c.rateLimiter.Allow() {
- log.Debugln("Client", c.ClientID, "has exceeded the messaging rate limiting thresholds.")
- return false
- }
-
- return true
-}
-
-// Listen read request via channel.
-func (c *Client) listenRead() {
- for {
- select {
- // receive done request
- case <-c.doneCh:
- _server.remove(c)
- c.doneCh <- true // for listenWrite method
- return
-
- // read data from websocket connection
- default:
- var data []byte
- if err := websocket.Message.Receive(c.ws, &data); err != nil {
- if err == io.EOF {
- c.doneCh <- true
- return
- }
- c.handleClientSocketError(err)
- }
-
- if !c.passesRateLimit() {
- continue
- }
-
- var messageTypeCheck map[string]interface{}
-
- // Bad messages should be thrown away
- if err := json.Unmarshal(data, &messageTypeCheck); err != nil {
- log.Debugln("Badly formatted message received from", c.Username, c.ws.Request().RemoteAddr)
- continue
- }
-
- // If we can't tell the type of message, also throw it away.
- if messageTypeCheck == nil {
- log.Debugln("Untyped message received from", c.Username, c.ws.Request().RemoteAddr)
- continue
- }
-
- messageType := messageTypeCheck["type"].(string)
-
- if messageType == models.MessageSent {
- c.chatMessageReceived(data)
- } else if messageType == models.UserNameChanged {
- c.userChangedName(data)
- } else if messageType == models.UserJoined {
- c.userJoined(data)
- }
- }
- }
-}
-
-func (c *Client) userJoined(data []byte) {
- var msg models.UserJoinedEvent
- if err := json.Unmarshal(data, &msg); err != nil {
- log.Errorln(err)
- return
- }
-
- msg.ID = shortid.MustGenerate()
- msg.Type = models.UserJoined
- msg.Timestamp = time.Now()
-
- c.Username = &msg.Username
-
- _server.userJoined(msg)
-}
-
-func (c *Client) userChangedName(data []byte) {
- var msg models.NameChangeEvent
- if err := json.Unmarshal(data, &msg); err != nil {
- log.Errorln(err)
- }
- msg.Type = models.UserNameChanged
- msg.ID = shortid.MustGenerate()
- _server.usernameChanged(msg)
- c.Username = &msg.NewName
-}
-
-func (c *Client) chatMessageReceived(data []byte) {
- var msg models.ChatEvent
- if err := json.Unmarshal(data, &msg); err != nil {
- log.Errorln(err)
- }
-
- msg.SetDefaults()
-
- c.MessageCount++
- c.Username = &msg.Author
-
- msg.ClientID = c.ClientID
- msg.RenderAndSanitizeMessageBody()
-
- _server.SendToAll(msg)
-}
-
-// GetViewerClientFromChatClient returns a general models.Client from a chat websocket client.
-func (c *Client) GetViewerClientFromChatClient() models.Client {
- return models.Client{
- ConnectedAt: c.ConnectedAt,
- MessageCount: c.MessageCount,
- UserAgent: c.UserAgent,
- IPAddress: c.IPAddress,
- Username: c.Username,
- ClientID: c.ClientID,
- Geo: geoip.GetGeoFromIP(c.IPAddress),
- }
-}
diff --git a/core/chat/events.go b/core/chat/events.go
new file mode 100644
index 000000000..9277c556d
--- /dev/null
+++ b/core/chat/events.go
@@ -0,0 +1,102 @@
+package chat
+
+import (
+ "encoding/json"
+ "fmt"
+ "strings"
+
+ "github.com/owncast/owncast/core/chat/events"
+ "github.com/owncast/owncast/core/data"
+ "github.com/owncast/owncast/core/user"
+ "github.com/owncast/owncast/core/webhooks"
+ log "github.com/sirupsen/logrus"
+)
+
+func (s *ChatServer) userNameChanged(eventData chatClientEvent) {
+ var receivedEvent events.NameChangeEvent
+ if err := json.Unmarshal(eventData.data, &receivedEvent); err != nil {
+ log.Errorln("error unmarshalling to NameChangeEvent", err)
+ return
+ }
+
+ proposedUsername := receivedEvent.NewName
+ blocklist := data.GetForbiddenUsernameList()
+
+ for _, blockedName := range blocklist {
+ normalizedName := strings.TrimSpace(blockedName)
+ normalizedName = strings.ToLower(normalizedName)
+ if strings.Contains(normalizedName, proposedUsername) {
+ // Denied.
+ log.Debugln(eventData.client.User.DisplayName, "blocked from changing name to", proposedUsername, "due to blocked name", normalizedName)
+ message := fmt.Sprintf("You cannot change your name to **%s**.", proposedUsername)
+ s.sendActionToClient(eventData.client, message)
+
+ // Resend the client's user so their username is in sync.
+ eventData.client.sendConnectedClientInfo()
+
+ return
+ }
+ }
+
+ savedUser := user.GetUserByToken(eventData.client.accessToken)
+ oldName := savedUser.DisplayName
+
+ // Save the new name
+ user.ChangeUsername(eventData.client.User.Id, receivedEvent.NewName)
+
+ // Update the connected clients associated user with the new name
+ eventData.client.User = savedUser
+
+ // Send chat event letting everyone about about the name change
+ savedUser.DisplayName = receivedEvent.NewName
+
+ broadcastEvent := events.NameChangeBroadcast{
+ Oldname: oldName,
+ }
+ broadcastEvent.User = savedUser
+ broadcastEvent.SetDefaults()
+ payload := broadcastEvent.GetBroadcastPayload()
+ if err := s.Broadcast(payload); err != nil {
+ log.Errorln("error broadcasting NameChangeEvent", err)
+ return
+ }
+
+ // Send chat user name changed webhook
+ receivedEvent.User = savedUser
+ webhooks.SendChatEventUsernameChanged(receivedEvent)
+}
+
+func (s *ChatServer) userMessageSent(eventData chatClientEvent) {
+ var event events.UserMessageEvent
+ if err := json.Unmarshal(eventData.data, &event); err != nil {
+ log.Errorln("error unmarshalling to UserMessageEvent", err)
+ return
+ }
+
+ event.SetDefaults()
+
+ // Ignore empty messages
+ if event.Empty() {
+ return
+ }
+
+ event.User = user.GetUserByToken(eventData.client.accessToken)
+
+ // Guard against nil users
+ if event.User == nil {
+ return
+ }
+
+ payload := event.GetBroadcastPayload()
+ if err := s.Broadcast(payload); err != nil {
+ log.Errorln("error broadcasting UserMessageEvent payload", err)
+ return
+ }
+
+ // Send chat message sent webhook
+ webhooks.SendChatEvent(&event)
+
+ SaveUserMessage(event)
+
+ eventData.client.MessageCount = eventData.client.MessageCount + 1
+}
diff --git a/core/chat/events/actionEvent.go b/core/chat/events/actionEvent.go
new file mode 100644
index 000000000..8f8093a03
--- /dev/null
+++ b/core/chat/events/actionEvent.go
@@ -0,0 +1,20 @@
+package events
+
+type ActionEvent struct {
+ Event
+ MessageEvent
+}
+
+// ActionEvent will return the object to send to all chat users.
+func (e *ActionEvent) GetBroadcastPayload() EventPayload {
+ return EventPayload{
+ "id": e.Id,
+ "timestamp": e.Timestamp,
+ "body": e.Body,
+ "type": e.GetMessageType(),
+ }
+}
+
+func (e *ActionEvent) GetMessageType() EventType {
+ return ChatActionSent
+}
diff --git a/models/chatMessage.go b/core/chat/events/events.go
similarity index 64%
rename from models/chatMessage.go
rename to core/chat/events/events.go
index d0e65b317..1fb998060 100644
--- a/models/chatMessage.go
+++ b/core/chat/events/events.go
@@ -1,4 +1,4 @@
-package models
+package events
import (
"bytes"
@@ -12,38 +12,59 @@ import (
"github.com/yuin/goldmark/extension"
"github.com/yuin/goldmark/renderer/html"
"mvdan.cc/xurls"
+
+ "github.com/owncast/owncast/core/user"
+ log "github.com/sirupsen/logrus"
)
-// ChatEvent represents a single chat message.
-type ChatEvent struct {
- ClientID string `json:"-"`
+// EventPayload is a generic key/value map for sending out to chat clients.
+type EventPayload map[string]interface{}
- Author string `json:"author,omitempty"`
- Body string `json:"body,omitempty"`
- RawBody string `json:"-"`
- ID string `json:"id"`
- MessageType EventType `json:"type"`
- Visible bool `json:"visible"`
- Timestamp time.Time `json:"timestamp,omitempty"`
- Ephemeral bool `json:"ephemeral,omitempty"`
+type OutboundEvent interface {
+ GetBroadcastPayload() EventPayload
+ GetMessageType() EventType
}
-// Valid checks to ensure the message is valid.
-func (m ChatEvent) Valid() bool {
- return m.Author != "" && m.Body != "" && m.ID != ""
+// Event is any kind of event. A type is required to be specified.
+type Event struct {
+ Type EventType `json:"type"`
+ Id string `json:"id"`
+ Timestamp time.Time `json:"timestamp"`
}
-// SetDefaults will set default values on a chat event object.
-func (m *ChatEvent) SetDefaults() {
- id, _ := shortid.Generate()
- m.ID = id
- m.Timestamp = time.Now()
- m.Visible = true
+type UserEvent struct {
+ User *user.User `json:"user"`
+ HiddenAt *time.Time `json:"hiddenAt,omitempty"`
+}
+
+// MessageEvent is an event that has a message body.
+type MessageEvent struct {
+ OutboundEvent `json:"-"`
+ Body string `json:"body"`
+ RawBody string `json:"-"`
+}
+
+type SystemActionEvent struct {
+ Event
+ MessageEvent
+}
+
+// SetDefaults will set default properties of all inbound events.
+func (e *Event) SetDefaults() {
+ e.Id = shortid.MustGenerate()
+ e.Timestamp = time.Now()
+}
+
+// SetDefaults will set default properties of all inbound events.
+func (e *UserMessageEvent) SetDefaults() {
+ e.Id = shortid.MustGenerate()
+ e.Timestamp = time.Now()
+ e.RenderAndSanitizeMessageBody()
}
// RenderAndSanitizeMessageBody will turn markdown into HTML, sanitize raw user-supplied HTML and standardize
// the message into something safe and renderable for clients.
-func (m *ChatEvent) RenderAndSanitizeMessageBody() {
+func (m *MessageEvent) RenderAndSanitizeMessageBody() {
m.RawBody = m.Body
// Set the new, sanitized and rendered message body
@@ -51,12 +72,12 @@ func (m *ChatEvent) RenderAndSanitizeMessageBody() {
}
// Empty will return if this message's contents is empty.
-func (m *ChatEvent) Empty() bool {
+func (m *MessageEvent) Empty() bool {
return m.Body == ""
}
-// RenderBody will render markdown to html without any sanitization
-func (m *ChatEvent) RenderBody() {
+// RenderBody will render markdown to html without any sanitization.
+func (m *MessageEvent) RenderBody() {
m.RawBody = m.Body
m.Body = RenderMarkdown(m.RawBody)
}
@@ -92,7 +113,7 @@ func RenderMarkdown(raw string) string {
trimmed := strings.TrimSpace(raw)
var buf bytes.Buffer
if err := markdown.Convert([]byte(trimmed), &buf); err != nil {
- panic(err)
+ log.Debugln(err)
}
return buf.String()
diff --git a/core/chat/events/eventtype.go b/core/chat/events/eventtype.go
new file mode 100644
index 000000000..aaed78257
--- /dev/null
+++ b/core/chat/events/eventtype.go
@@ -0,0 +1,34 @@
+package events
+
+// EventType is the type of a websocket event.
+type EventType = string
+
+const (
+ // MessageSent is the event sent when a chat event takes place.
+ MessageSent EventType = "CHAT"
+ // UserJoined is the event sent when a chat user join action takes place.
+ 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"
+ // PING is a ping message.
+ PING EventType = "PING"
+ // PONG is a pong message.
+ PONG EventType = "PONG"
+ // StreamStarted represents a stream started event.
+ StreamStarted EventType = "STREAM_STARTED"
+ // StreamStopped represents a stream stopped event.
+ StreamStopped EventType = "STREAM_STOPPED"
+ // SystemMessageSent is the event sent when a system message is sent.
+ SystemMessageSent EventType = "SYSTEM"
+ // ChatDisabled is when a user is explicitly disabled and blocked from using chat.
+ ChatDisabled EventType = "CHAT_DISABLED"
+ // ConnectedUserInfo is a private event to a user letting them know their user details.
+ ConnectedUserInfo EventType = "CONNECTED_USER_INFO"
+ // ChatActionSent is a generic chat action that can be used for anything that doesn't need specific handling or formatting.
+ ChatActionSent EventType = "CHAT_ACTION"
+ ErrorNeedsRegistration EventType = "ERROR_NEEDS_REGISTRATION"
+ ErrorMaxConnectionsExceeded EventType = "ERROR_MAX_CONNECTIONS_EXCEEDED"
+ ErrorUserDisabled EventType = "ERROR_USER_DISABLED"
+)
diff --git a/core/chat/events/nameChangeEvent.go b/core/chat/events/nameChangeEvent.go
new file mode 100644
index 000000000..7eb898644
--- /dev/null
+++ b/core/chat/events/nameChangeEvent.go
@@ -0,0 +1,26 @@
+package events
+
+// NameChangeEvent is received when a user changes their chat display name.
+type NameChangeEvent struct {
+ Event
+ UserEvent
+ NewName string `json:"newName"`
+}
+
+// NameChangeEventBroadcast is fired when a user changes their chat display name.
+type NameChangeBroadcast struct {
+ Event
+ UserEvent
+ Oldname string `json:"oldName"`
+}
+
+// GetBroadcastPayload will return the object to send to all chat users.
+func (e *NameChangeBroadcast) GetBroadcastPayload() EventPayload {
+ return EventPayload{
+ "id": e.Id,
+ "timestamp": e.Timestamp,
+ "user": e.User,
+ "oldName": e.Oldname,
+ "type": UserNameChanged,
+ }
+}
diff --git a/core/chat/events/systemMessageEvent.go b/core/chat/events/systemMessageEvent.go
new file mode 100644
index 000000000..0bb79081c
--- /dev/null
+++ b/core/chat/events/systemMessageEvent.go
@@ -0,0 +1,26 @@
+package events
+
+import "github.com/owncast/owncast/core/data"
+
+// SystemMessageEvent is a message displayed in chat on behalf of the server.
+type SystemMessageEvent struct {
+ Event
+ MessageEvent
+}
+
+// SystemMessageEvent will return the object to send to all chat users.
+func (e *SystemMessageEvent) GetBroadcastPayload() EventPayload {
+ return EventPayload{
+ "id": e.Id,
+ "timestamp": e.Timestamp,
+ "body": e.Body,
+ "type": SystemMessageSent,
+ "user": EventPayload{
+ "displayName": data.GetServerName(),
+ },
+ }
+}
+
+func (e *SystemMessageEvent) GetMessageType() EventType {
+ return SystemMessageSent
+}
diff --git a/core/chat/events/userDisabledEvent.go b/core/chat/events/userDisabledEvent.go
new file mode 100644
index 000000000..4f5b4b3c9
--- /dev/null
+++ b/core/chat/events/userDisabledEvent.go
@@ -0,0 +1,17 @@
+package events
+
+// UserDisabledEvent is the event fired when a user is banned/blocked and disconnected from chat.
+type UserDisabledEvent struct {
+ Event
+ UserEvent
+}
+
+// GetBroadcastPayload will return the object to send to all chat users.
+func (e *UserDisabledEvent) GetBroadcastPayload() EventPayload {
+ return EventPayload{
+ "type": ErrorUserDisabled,
+ "id": e.Id,
+ "timestamp": e.Timestamp,
+ "user": e.User,
+ }
+}
diff --git a/core/chat/events/userJoinedEvent.go b/core/chat/events/userJoinedEvent.go
new file mode 100644
index 000000000..dfa332fdf
--- /dev/null
+++ b/core/chat/events/userJoinedEvent.go
@@ -0,0 +1,17 @@
+package events
+
+// UserJoinedEvent is the event fired when a user joins chat.
+type UserJoinedEvent struct {
+ Event
+ UserEvent
+}
+
+// GetBroadcastPayload will return the object to send to all chat users.
+func (e *UserJoinedEvent) GetBroadcastPayload() EventPayload {
+ return EventPayload{
+ "type": UserJoined,
+ "id": e.Id,
+ "timestamp": e.Timestamp,
+ "user": e.User,
+ }
+}
diff --git a/core/chat/events/userMessageEvent.go b/core/chat/events/userMessageEvent.go
new file mode 100644
index 000000000..63f9fb567
--- /dev/null
+++ b/core/chat/events/userMessageEvent.go
@@ -0,0 +1,24 @@
+package events
+
+// UserMessageEvent is an inbound message from a user.
+type UserMessageEvent struct {
+ Event
+ UserEvent
+ MessageEvent
+}
+
+// GetBroadcastPayload will return the object to send to all chat users.
+func (e *UserMessageEvent) GetBroadcastPayload() EventPayload {
+ return EventPayload{
+ "id": e.Id,
+ "timestamp": e.Timestamp,
+ "body": e.Body,
+ "user": e.User,
+ "type": MessageSent,
+ "visible": e.HiddenAt == nil,
+ }
+}
+
+func (e *UserMessageEvent) GetMessageType() EventType {
+ return MessageSent
+}
diff --git a/core/chat/messageRendering_test.go b/core/chat/messageRendering_test.go
index e5cf552f0..609b5d3ae 100644
--- a/core/chat/messageRendering_test.go
+++ b/core/chat/messageRendering_test.go
@@ -3,7 +3,7 @@ package chat
import (
"testing"
- "github.com/owncast/owncast/models"
+ "github.com/owncast/owncast/core/chat/events"
)
// Test a bunch of arbitrary markup and markdown to make sure we get sanitized
@@ -25,7 +25,7 @@ blah blah blah
test link
`
- result := models.RenderAndSanitize(messageContent)
+ result := events.RenderAndSanitize(messageContent)
if result != expected {
t.Errorf("message rendering/sanitation does not match expected. Got\n%s, \n\n want:\n%s", result, expected)
}
@@ -35,7 +35,7 @@ blah blah blah
func TestBlockRemoteImages(t *testing.T) {
messageContent := ` test `
expected := ` test
`
- result := models.RenderAndSanitize(messageContent)
+ result := events.RenderAndSanitize(messageContent)
if result != expected {
t.Errorf("message rendering/sanitation does not match expected. Got\n%s, \n\n want:\n%s", result, expected)
@@ -46,7 +46,7 @@ func TestBlockRemoteImages(t *testing.T) {
func TestAllowEmojiImages(t *testing.T) {
messageContent := ` test `
expected := ` test
`
- result := models.RenderAndSanitize(messageContent)
+ result := events.RenderAndSanitize(messageContent)
if result != expected {
t.Errorf("message rendering/sanitation does not match expected. Got\n%s, \n\n want:\n%s", result, expected)
@@ -57,7 +57,7 @@ func TestAllowEmojiImages(t *testing.T) {
func TestAllowHTML(t *testing.T) {
messageContent := ` `
expected := "
\n"
- result := models.RenderMarkdown(messageContent)
+ result := events.RenderMarkdown(messageContent)
if result != expected {
t.Errorf("message rendering does not match expected. Got\n%s, \n\n want:\n%s", result, expected)
diff --git a/core/chat/messages.go b/core/chat/messages.go
index 8d5dfa3e9..9d35be716 100644
--- a/core/chat/messages.go
+++ b/core/chat/messages.go
@@ -1,8 +1,8 @@
package chat
import (
+ "github.com/owncast/owncast/core/chat/events"
"github.com/owncast/owncast/core/webhooks"
- "github.com/owncast/owncast/models"
log "github.com/sirupsen/logrus"
)
@@ -22,8 +22,11 @@ func SetMessagesVisibility(messageIDs []string, visibility bool) error {
log.Errorln(err)
continue
}
- message.MessageType = models.VisibiltyToggled
- _server.sendAll(message)
+ payload := message.GetBroadcastPayload()
+ payload["type"] = events.VisibiltyToggled
+ if err := _server.Broadcast(payload); err != nil {
+ log.Debugln(err)
+ }
go webhooks.SendChatEvent(message)
}
diff --git a/core/chat/persistence.go b/core/chat/persistence.go
index c25bc118e..4940ebc09 100644
--- a/core/chat/persistence.go
+++ b/core/chat/persistence.go
@@ -1,171 +1,309 @@
package chat
import (
- "database/sql"
+ "fmt"
"strings"
"time"
_ "github.com/mattn/go-sqlite3"
+ "github.com/owncast/owncast/core/chat/events"
"github.com/owncast/owncast/core/data"
+ "github.com/owncast/owncast/core/user"
"github.com/owncast/owncast/models"
log "github.com/sirupsen/logrus"
)
-var _db *sql.DB
+var _datastore *data.Datastore
+
+const (
+ maxBacklogHours = 5 // Keep backlog max hours worth of messages
+ maxBacklogNumber = 50 // Return max number of messages in history request
+)
func setupPersistence() {
- _db = data.GetDatabase()
- createTable()
+ _datastore = data.GetDatastore()
+ createMessagesTable()
+
+ chatDataPruner := time.NewTicker(5 * time.Minute)
+ go func() {
+ runPruner()
+ for range chatDataPruner.C {
+ runPruner()
+ }
+ }()
}
-func createTable() {
+func createMessagesTable() {
createTableSQL := `CREATE TABLE IF NOT EXISTS messages (
"id" string NOT NULL PRIMARY KEY,
- "author" TEXT,
+ "user_id" INTEGER,
"body" TEXT,
- "messageType" TEXT,
- "visible" INTEGER,
- "timestamp" DATE
+ "eventType" TEXT,
+ "hidden_at" DATETIME,
+ "timestamp" DATETIME
);`
- stmt, err := _db.Prepare(createTableSQL)
+ stmt, err := _datastore.DB.Prepare(createTableSQL)
if err != nil {
- log.Fatal(err)
+ log.Fatal("error creating chat messages table", err)
}
defer stmt.Close()
if _, err := stmt.Exec(); err != nil {
- log.Warnln(err)
+ log.Fatal("error creating chat messages table", err)
}
}
-func addMessage(message models.ChatEvent) {
- tx, err := _db.Begin()
- if err != nil {
- log.Fatal(err)
- }
- stmt, err := tx.Prepare("INSERT INTO messages(id, author, body, messageType, visible, timestamp) values(?, ?, ?, ?, ?, ?)")
+func SaveUserMessage(event events.UserMessageEvent) {
+ saveEvent(event.Id, event.User.Id, event.Body, event.Type, event.HiddenAt, event.Timestamp)
+}
+func saveEvent(id string, userId string, body string, eventType string, hidden *time.Time, timestamp time.Time) {
+ _datastore.DbLock.Lock()
+ defer _datastore.DbLock.Unlock()
+
+ tx, err := _datastore.DB.Begin()
if err != nil {
- log.Fatal(err)
+ log.Errorln("error saving", eventType, err)
+ return
}
+
+ defer tx.Rollback() // nolint
+
+ stmt, err := tx.Prepare("INSERT INTO messages(id, user_id, body, eventType, hidden_at, timestamp) values(?, ?, ?, ?, ?, ?)")
+ if err != nil {
+ log.Errorln("error saving", eventType, err)
+ return
+ }
+
defer stmt.Close()
- if _, err := stmt.Exec(message.ID, message.Author, message.Body, message.MessageType, 1, message.Timestamp); err != nil {
- log.Fatal(err)
+ if _, err = stmt.Exec(id, userId, body, eventType, hidden, timestamp); err != nil {
+ log.Errorln("error saving", eventType, err)
+ return
}
- if err := tx.Commit(); err != nil {
- log.Fatal(err)
+ if err = tx.Commit(); err != nil {
+ log.Errorln("error saving", eventType, err)
+ return
}
}
-func getChat(query string) []models.ChatEvent {
- history := make([]models.ChatEvent, 0)
- rows, err := _db.Query(query)
+func getChat(query string) []events.UserMessageEvent {
+ history := make([]events.UserMessageEvent, 0)
+ rows, err := _datastore.DB.Query(query)
if err != nil {
- log.Fatal(err)
+ log.Errorln("error fetching chat history", err)
+ return history
}
defer rows.Close()
for rows.Next() {
var id string
- var author string
+ var userId string
var body string
var messageType models.EventType
- var visible int
+ var hiddenAt *time.Time
var timestamp time.Time
- err = rows.Scan(&id, &author, &body, &messageType, &visible, ×tamp)
+ var userDisplayName *string
+ var userDisplayColor *int
+ var userCreatedAt *time.Time
+ var userDisabledAt *time.Time
+ var previousUsernames *string
+ var userNameChangedAt *time.Time
+
+ // Convert a database row into a chat event
+ err = rows.Scan(&id, &userId, &body, &messageType, &hiddenAt, ×tamp, &userDisplayName, &userDisplayColor, &userCreatedAt, &userDisabledAt, &previousUsernames, &userNameChangedAt)
if err != nil {
- log.Debugln(err)
- log.Error("There is a problem with the chat database. Restore a backup of owncast.db or remove it and start over.")
+ log.Errorln("There is a problem converting query to chat objects. Please report this:", query)
break
}
- message := models.ChatEvent{}
- message.ID = id
- message.Author = author
- message.Body = body
- message.MessageType = messageType
- message.Visible = visible == 1
- message.Timestamp = timestamp
+ // System messages and chat actions are special and are not from real users
+ if messageType == events.SystemMessageSent || messageType == events.ChatActionSent {
+ name := "Owncast"
+ userDisplayName = &name
+ color := 200
+ userDisplayColor = &color
+ }
+
+ if previousUsernames == nil {
+ previousUsernames = userDisplayName
+ }
+
+ if userCreatedAt == nil {
+ now := time.Now()
+ userCreatedAt = &now
+ }
+
+ user := user.User{
+ Id: userId,
+ AccessToken: "",
+ DisplayName: *userDisplayName,
+ DisplayColor: *userDisplayColor,
+ CreatedAt: *userCreatedAt,
+ DisabledAt: userDisabledAt,
+ NameChangedAt: userNameChangedAt,
+ PreviousNames: strings.Split(*previousUsernames, ","),
+ }
+
+ message := events.UserMessageEvent{
+ Event: events.Event{
+ Type: messageType,
+ Id: id,
+ Timestamp: timestamp,
+ },
+ UserEvent: events.UserEvent{
+ User: &user,
+ HiddenAt: hiddenAt,
+ },
+ MessageEvent: events.MessageEvent{
+ Body: body,
+ RawBody: body,
+ },
+ }
history = append(history, message)
}
- if err := rows.Err(); err != nil {
- log.Fatal(err)
- }
-
return history
}
-func getChatModerationHistory() []models.ChatEvent {
- var query = "SELECT * FROM messages WHERE messageType == 'CHAT' AND datetime(timestamp) >=datetime('now', '-5 Hour')"
+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"
return getChat(query)
}
-func getChatHistory() []models.ChatEvent {
- // Get all messages sent within the past 5hrs, max 50
- var query = "SELECT * FROM (SELECT * FROM messages WHERE datetime(timestamp) >=datetime('now', '-5 Hour') AND visible = 1 ORDER BY timestamp DESC LIMIT 50) ORDER BY timestamp asc"
+func GetChatHistory() []events.UserMessageEvent {
+ // Get all visible messages
+ var query = fmt.Sprintf("SELECT id, user_id, body, eventType, hidden_at, timestamp, display_name, display_color, created_at, disabled_at, previous_names, namechanged_at FROM (SELECT * FROM messages LEFT OUTER JOIN users ON messages.user_id = users.id WHERE hidden_at IS NULL ORDER BY timestamp DESC LIMIT %d) ORDER BY timestamp asc", maxBacklogNumber)
return getChat(query)
}
+// SetMessageVisibilityForUserId will bulk change the visibility of messages for a user
+// and then send out visibility changed events to chat clients.
+func SetMessageVisibilityForUserId(userID string, visible bool) error {
+ // Get a list of IDs from this user within the 5hr window to send to the connected clients to hide
+ ids := make([]string, 0)
+ 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 INNER JOIN users ON messages.user_id = users.id WHERE user_id IS '%s'", userID)
+ messages := getChat(query)
+
+ if len(messages) == 0 {
+ return nil
+ }
+
+ for _, message := range messages {
+ ids = append(ids, message.Id)
+ }
+
+ // Tell the clients to hide/show these messages.
+ return SetMessagesVisibility(ids, visible)
+}
+
func saveMessageVisibility(messageIDs []string, visible bool) error {
- tx, err := _db.Begin()
+ _datastore.DbLock.Lock()
+ defer _datastore.DbLock.Unlock()
+
+ tx, err := _datastore.DB.Begin()
if err != nil {
- log.Fatal(err)
+ return err
}
- stmt, err := tx.Prepare("UPDATE messages SET visible=? WHERE id IN (?" + strings.Repeat(",?", len(messageIDs)-1) + ")")
+ stmt, err := tx.Prepare("UPDATE messages SET hidden_at=? WHERE id IN (?" + strings.Repeat(",?", len(messageIDs)-1) + ")")
if err != nil {
- log.Fatal(err)
return err
}
defer stmt.Close()
+ var hiddenAt *time.Time
+ if !visible {
+ now := time.Now()
+ hiddenAt = &now
+ } else {
+ hiddenAt = nil
+ }
+
args := make([]interface{}, len(messageIDs)+1)
- args[0] = visible
+ args[0] = hiddenAt
for i, id := range messageIDs {
args[i+1] = id
}
- if _, err := stmt.Exec(args...); err != nil {
- log.Fatal(err)
+ if _, err = stmt.Exec(args...); err != nil {
return err
}
- if err := tx.Commit(); err != nil {
- log.Fatal(err)
+ if err = tx.Commit(); err != nil {
return err
}
return nil
}
-func getMessageById(messageID string) (models.ChatEvent, error) {
+func getMessageById(messageID string) (*events.UserMessageEvent, error) {
var query = "SELECT * FROM messages WHERE id = ?"
- row := _db.QueryRow(query, messageID)
+ row := _datastore.DB.QueryRow(query, messageID)
var id string
- var author string
+ var userId string
var body string
- var messageType models.EventType
- var visible int
+ var eventType models.EventType
+ var hiddenAt *time.Time
var timestamp time.Time
- err := row.Scan(&id, &author, &body, &messageType, &visible, ×tamp)
+ err := row.Scan(&id, &userId, &body, &eventType, &hiddenAt, ×tamp)
if err != nil {
log.Errorln(err)
- return models.ChatEvent{}, err
+ return nil, err
}
- return models.ChatEvent{
- ID: id,
- Author: author,
- Body: body,
- MessageType: messageType,
- Visible: visible == 1,
- Timestamp: timestamp,
+ 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() {
+ _datastore.DbLock.Lock()
+ defer _datastore.DbLock.Unlock()
+
+ log.Traceln("Removing chat messages older than", maxBacklogHours, "hours")
+
+ deleteStatement := `DELETE FROM messages WHERE timestamp <= datetime('now', 'localtime', ?)`
+ tx, err := _datastore.DB.Begin()
+ if err != nil {
+ log.Debugln(err)
+ return
+ }
+
+ stmt, err := tx.Prepare(deleteStatement)
+ if err != nil {
+ log.Debugln(err)
+ return
+ }
+ defer stmt.Close()
+
+ if _, err = stmt.Exec(fmt.Sprintf("-%d hours", maxBacklogHours)); err != nil {
+ log.Debugln(err)
+ return
+ }
+ if err = tx.Commit(); err != nil {
+ log.Debugln(err)
+ return
+ }
+}
diff --git a/core/chat/server.go b/core/chat/server.go
index 01dfada58..55388bd1a 100644
--- a/core/chat/server.go
+++ b/core/chat/server.go
@@ -1,191 +1,317 @@
package chat
import (
- "fmt"
+ "encoding/json"
"net/http"
"sync"
"time"
log "github.com/sirupsen/logrus"
- "golang.org/x/net/websocket"
+ "github.com/gorilla/websocket"
+
+ "github.com/owncast/owncast/core/chat/events"
"github.com/owncast/owncast/core/data"
+ "github.com/owncast/owncast/core/user"
"github.com/owncast/owncast/core/webhooks"
- "github.com/owncast/owncast/models"
+ "github.com/owncast/owncast/utils"
)
-var (
- _server *server
-)
+var _server *ChatServer
-var l = &sync.RWMutex{}
+type ChatServer struct {
+ mu sync.RWMutex
+ seq uint
+ clients map[uint]*ChatClient
+ maxClientCount uint
-// Server represents the server which handles the chat.
-type server struct {
- Clients map[string]*Client
+ // send outbound message payload to all clients
+ outbound chan []byte
- pattern string
- listener models.ChatListener
+ // receive inbound message payload from all clients
+ inbound chan chatClientEvent
- addCh chan *Client
- delCh chan *Client
- sendAllCh chan models.ChatEvent
- pingCh chan models.PingMessage
- doneCh chan bool
- errCh chan error
+ // unregister requests from clients.
+ unregister chan *ChatClient
}
-// Add adds a client to the server.
-func (s *server) add(c *Client) {
- s.addCh <- c
-}
-
-// Remove removes a client from the server.
-func (s *server) remove(c *Client) {
- s.delCh <- c
-}
-
-// SendToAll sends a message to all of the connected clients.
-func (s *server) SendToAll(msg models.ChatEvent) {
- s.sendAllCh <- msg
-}
-
-// Err handles an error.
-func (s *server) err(err error) {
- s.errCh <- err
-}
-
-func (s *server) sendAll(msg models.ChatEvent) {
- l.RLock()
- for _, c := range s.Clients {
- c.write(msg)
+func NewChat() *ChatServer {
+ server := &ChatServer{
+ clients: map[uint]*ChatClient{},
+ outbound: make(chan []byte),
+ inbound: make(chan chatClientEvent),
+ unregister: make(chan *ChatClient),
+ maxClientCount: handleMaxConnectionCount(),
}
- l.RUnlock()
+
+ return server
}
-func (s *server) ping() {
- ping := models.PingMessage{MessageType: models.PING}
-
- l.RLock()
- for _, c := range s.Clients {
- c.pingch <- ping
- }
- l.RUnlock()
-}
-
-func (s *server) usernameChanged(msg models.NameChangeEvent) {
- l.RLock()
- for _, c := range s.Clients {
- c.usernameChangeChannel <- msg
- }
- l.RUnlock()
-
- go webhooks.SendChatEventUsernameChanged(msg)
-}
-
-func (s *server) userJoined(msg models.UserJoinedEvent) {
- l.RLock()
- if s.listener.IsStreamConnected() {
- for _, c := range s.Clients {
- c.userJoinedChannel <- msg
- }
- }
- l.RUnlock()
-
- go webhooks.SendChatEventUserJoined(msg)
-}
-
-func (s *server) onConnection(ws *websocket.Conn) {
- client := NewClient(ws)
-
- defer func() {
- s.removeClient(client)
-
- if err := ws.Close(); err != nil {
- log.Debugln(err)
- //s.errCh <- err
- }
- }()
-
- s.add(client)
- client.listen()
-}
-
-// Listen and serve.
-// It serves client connection and broadcast request.
-func (s *server) Listen() {
- http.Handle(s.pattern, websocket.Handler(s.onConnection))
-
- log.Tracef("Starting the websocket listener on: %s", s.pattern)
-
+func (s *ChatServer) Run() {
for {
select {
- // add new a client
- case c := <-s.addCh:
- l.Lock()
- s.Clients[c.socketID] = c
-
- if !c.Ignore {
- s.listener.ClientAdded(c.GetViewerClientFromChatClient())
- s.sendWelcomeMessageToClient(c)
- }
- l.Unlock()
-
- // remove a client
- case c := <-s.delCh:
- s.removeClient(c)
- case msg := <-s.sendAllCh:
- if data.GetChatDisabled() {
- break
+ case client := <-s.unregister:
+ if _, ok := s.clients[client.id]; ok {
+ s.mu.Lock()
+ delete(s.clients, client.id)
+ close(client.send)
+ s.mu.Unlock()
}
- if !msg.Empty() {
- // set defaults before sending msg to anywhere
- msg.SetDefaults()
-
- s.listener.MessageSent(msg)
- s.sendAll(msg)
-
- // Store in the message history
- if !msg.Ephemeral {
- addMessage(msg)
- }
-
- // Send webhooks
- go webhooks.SendChatEvent(msg)
- }
- case ping := <-s.pingCh:
- fmt.Println("PING?", ping)
-
- case err := <-s.errCh:
- log.Trace("Error: ", err.Error())
-
- case <-s.doneCh:
- return
+ case message := <-s.inbound:
+ s.eventReceived(message)
}
}
}
-func (s *server) removeClient(c *Client) {
- l.Lock()
- if _, ok := s.Clients[c.socketID]; ok {
- delete(s.Clients, c.socketID)
-
- s.listener.ClientRemoved(c.socketID)
- log.Tracef("The client was connected for %s and sent %d messages (%s)", time.Since(c.ConnectedAt), c.MessageCount, c.ClientID)
+// Addclient registers new connection as a User.
+func (s *ChatServer) Addclient(conn *websocket.Conn, user *user.User, accessToken string, userAgent string) *ChatClient {
+ client := &ChatClient{
+ server: s,
+ conn: conn,
+ User: user,
+ ipAddress: conn.RemoteAddr().String(),
+ accessToken: accessToken,
+ send: make(chan []byte, 256),
+ UserAgent: userAgent,
+ ConnectedAt: time.Now(),
}
- l.Unlock()
+
+ s.mu.Lock()
+ {
+ client.id = s.seq
+ s.clients[client.id] = client
+ s.seq++
+ }
+ s.mu.Unlock()
+
+ log.Traceln("Adding client", client.id, "total count:", len(s.clients))
+
+ go client.writePump()
+ go client.readPump()
+
+ client.sendConnectedClientInfo()
+
+ if getStatus().Online {
+ s.sendUserJoinedMessage(client)
+ s.sendWelcomeMessageToClient(client)
+ }
+
+ return client
}
-func (s *server) sendWelcomeMessageToClient(c *Client) {
- go func() {
- // Add an artificial delay so people notice this message come in.
- time.Sleep(7 * time.Second)
+func (s *ChatServer) sendUserJoinedMessage(c *ChatClient) {
+ userJoinedEvent := events.UserJoinedEvent{}
+ userJoinedEvent.SetDefaults()
+ userJoinedEvent.User = c.User
- welcomeMessage := data.GetServerWelcomeMessage()
- if welcomeMessage != "" {
- initialMessage := models.ChatEvent{ClientID: "owncast-server", Author: data.GetServerName(), Body: welcomeMessage, ID: "initial-message-1", MessageType: "SYSTEM", Visible: true, Timestamp: time.Now()}
- c.write(initialMessage)
+ if err := s.Broadcast(userJoinedEvent.GetBroadcastPayload()); err != nil {
+ log.Errorln("error adding client to chat server", err)
+ }
+
+ // Send chat user joined webhook
+ webhooks.SendChatEventUserJoined(userJoinedEvent)
+}
+
+func (s *ChatServer) ClientClosed(c *ChatClient) {
+ s.mu.Lock()
+ defer s.mu.Unlock()
+ c.close()
+
+ if _, ok := s.clients[c.id]; ok {
+ log.Debugln("Deleting", c.id)
+ delete(s.clients, c.id)
+ }
+}
+
+func (s *ChatServer) HandleClientConnection(w http.ResponseWriter, r *http.Request) {
+ if data.GetChatDisabled() {
+ _, _ = w.Write([]byte(events.ChatDisabled))
+ return
+ }
+
+ // Limit concurrent chat connections
+ if uint(len(s.clients)) >= s.maxClientCount {
+ log.Warnln("rejecting incoming client connection as it exceeds the max client count of", s.maxClientCount)
+ _, _ = w.Write([]byte(events.ErrorMaxConnectionsExceeded))
+ return
+ }
+
+ conn, err := upgrader.Upgrade(w, r, nil)
+ if err != nil {
+ log.Debugln(err)
+ return
+ }
+
+ accessToken := r.URL.Query().Get("accessToken")
+ if accessToken == "" {
+ log.Errorln("Access token is required")
+ // Return HTTP status code
+ conn.Close()
+ return
+ }
+
+ // A user is required to use the websocket
+ user := user.GetUserByToken(accessToken)
+ if user == nil {
+ _ = conn.WriteJSON(events.EventPayload{
+ "type": events.ErrorNeedsRegistration,
+ })
+ // Send error that registration is required
+ conn.Close()
+ return
+ }
+
+ // User is disabled therefore we should disconnect.
+ if user.DisabledAt != nil {
+ log.Traceln("Disabled user", user.Id, user.DisplayName, "rejected")
+ _ = conn.WriteJSON(events.EventPayload{
+ "type": events.ErrorUserDisabled,
+ })
+ conn.Close()
+ return
+ }
+
+ userAgent := r.UserAgent()
+
+ s.Addclient(conn, user, accessToken, userAgent)
+}
+
+// Broadcast sends message to all connected clients.
+func (s *ChatServer) Broadcast(payload events.EventPayload) error {
+ data, err := json.Marshal(payload)
+ if err != nil {
+ return err
+ }
+
+ s.mu.Lock()
+ defer s.mu.Unlock()
+
+ for _, client := range s.clients {
+ if client == nil {
+ continue
}
- }()
+
+ select {
+ case client.send <- data:
+ default:
+ close(client.send)
+ delete(s.clients, client.id)
+ }
+ }
+
+ return nil
+}
+
+func (s *ChatServer) Send(payload events.EventPayload, client *ChatClient) {
+ data, err := json.Marshal(payload)
+ if err != nil {
+ log.Errorln(err)
+ return
+ }
+
+ client.send <- data
+}
+
+// DisconnectUser will forcefully disconnect all clients belonging to a user by ID.
+func (s *ChatServer) DisconnectUser(userID string) {
+ s.mu.Lock()
+ clients, err := GetClientsForUser(userID)
+ s.mu.Unlock()
+
+ if err != nil || clients == nil || len(clients) == 0 {
+ log.Debugln("Requested to disconnect user", userID, err)
+ return
+ }
+
+ for _, client := range clients {
+ log.Traceln("Disconnecting client", client.User.Id, "owned by", client.User.DisplayName)
+
+ go func(client *ChatClient) {
+ event := events.UserDisabledEvent{}
+ event.SetDefaults()
+
+ // Send this disabled event specifically to this single connected client
+ // to let them know they've been banned.
+ _server.Send(event.GetBroadcastPayload(), client)
+
+ // Give the socket time to send out the above message.
+ // Unfortunately I don't know of any way to get a real callback to know when
+ // the message was successfully sent, so give it a couple seconds.
+ time.Sleep(2 * time.Second)
+
+ // Forcefully disconnect if still valid.
+ if client != nil {
+ client.close()
+ }
+ }(client)
+ }
+}
+
+func (s *ChatServer) eventReceived(event chatClientEvent) {
+ var typecheck map[string]interface{}
+ if err := json.Unmarshal(event.data, &typecheck); err != nil {
+ log.Debugln(err)
+ }
+
+ eventType := typecheck["type"]
+
+ switch eventType {
+ case events.MessageSent:
+ s.userMessageSent(event)
+
+ case events.UserNameChanged:
+ s.userNameChanged(event)
+
+ default:
+ log.Debugln(eventType, "event not found:", typecheck)
+ }
+}
+
+func (s *ChatServer) sendWelcomeMessageToClient(c *ChatClient) {
+ // Add an artificial delay so people notice this message come in.
+ time.Sleep(7 * time.Second)
+
+ welcomeMessage := utils.RenderSimpleMarkdown(data.GetServerWelcomeMessage())
+
+ if welcomeMessage != "" {
+ s.sendSystemMessageToClient(c, welcomeMessage)
+ }
+}
+
+func (s *ChatServer) sendAllWelcomeMessage() {
+ welcomeMessage := utils.RenderSimpleMarkdown(data.GetServerWelcomeMessage())
+
+ if welcomeMessage != "" {
+ clientMessage := events.SystemMessageEvent{
+ Event: events.Event{},
+ MessageEvent: events.MessageEvent{
+ Body: welcomeMessage,
+ },
+ }
+ clientMessage.SetDefaults()
+ _ = s.Broadcast(clientMessage.GetBroadcastPayload())
+ }
+}
+
+func (s *ChatServer) sendSystemMessageToClient(c *ChatClient, message string) {
+ clientMessage := events.SystemMessageEvent{
+ Event: events.Event{},
+ MessageEvent: events.MessageEvent{
+ Body: message,
+ },
+ }
+ clientMessage.SetDefaults()
+ s.Send(clientMessage.GetBroadcastPayload(), c)
+}
+
+func (s *ChatServer) sendActionToClient(c *ChatClient, message string) {
+ clientMessage := events.ActionEvent{
+ MessageEvent: events.MessageEvent{
+ Body: message,
+ },
+ }
+ clientMessage.SetDefaults()
+ clientMessage.RenderBody()
+ s.Send(clientMessage.GetBroadcastPayload(), c)
}
diff --git a/core/chat/utils.go b/core/chat/utils.go
new file mode 100644
index 000000000..1e69b93e4
--- /dev/null
+++ b/core/chat/utils.go
@@ -0,0 +1,29 @@
+package chat
+
+import (
+ "syscall"
+
+ log "github.com/sirupsen/logrus"
+)
+
+// Set the soft file handler limit as 70% of
+// the max as the client connection limit.
+func handleMaxConnectionCount() uint {
+ var rLimit syscall.Rlimit
+ if err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &rLimit); err != nil {
+ panic(err)
+ }
+
+ originalLimit := rLimit.Cur
+ // Set the limit to 70% of max so the machine doesn't die even if it's maxed out for some reason.
+ proposedLimit := int(float32(rLimit.Max) * 0.7)
+
+ rLimit.Cur = uint64(proposedLimit)
+ if err := syscall.Setrlimit(syscall.RLIMIT_NOFILE, &rLimit); err != nil {
+ panic(err)
+ }
+
+ log.Traceln("Max process connection count increased from", originalLimit, "to", proposedLimit)
+
+ return uint(float32(rLimit.Cur))
+}
diff --git a/core/chatListener.go b/core/chatListener.go
deleted file mode 100644
index b868b97dc..000000000
--- a/core/chatListener.go
+++ /dev/null
@@ -1,44 +0,0 @@
-package core
-
-import (
- "github.com/owncast/owncast/core/chat"
- "github.com/owncast/owncast/models"
-)
-
-// ChatListenerImpl the implementation of the chat client.
-type ChatListenerImpl struct{}
-
-// ClientAdded is for when a client is added the system.
-func (cl ChatListenerImpl) ClientAdded(client models.Client) {
- SetChatClientActive(client)
-}
-
-// ClientRemoved is for when a client disconnects/is removed.
-func (cl ChatListenerImpl) ClientRemoved(clientID string) {
- RemoveChatClient(clientID)
-}
-
-// MessageSent is for when a message is sent.
-func (cl ChatListenerImpl) MessageSent(message models.ChatEvent) {
-}
-
-// IsStreamConnected will return if the stream is connected.
-func (cl ChatListenerImpl) IsStreamConnected() bool {
- return IsStreamConnected()
-}
-
-// SendMessageToChat sends a message to the chat server.
-func SendMessageToChat(message models.ChatEvent) error {
- chat.SendMessage(message)
-
- return nil
-}
-
-// GetAllChatMessages gets all of the chat messages.
-func GetAllChatMessages() []models.ChatEvent {
- return chat.GetMessages()
-}
-
-func GetModerationChatMessages() []models.ChatEvent {
- return chat.GetModerationChatMessages()
-}
diff --git a/core/core.go b/core/core.go
index 2bd5672d1..a9818d3d7 100644
--- a/core/core.go
+++ b/core/core.go
@@ -12,6 +12,7 @@ import (
"github.com/owncast/owncast/core/data"
"github.com/owncast/owncast/core/rtmp"
"github.com/owncast/owncast/core/transcoder"
+ "github.com/owncast/owncast/core/user"
"github.com/owncast/owncast/models"
"github.com/owncast/owncast/utils"
"github.com/owncast/owncast/yp"
@@ -53,6 +54,8 @@ func Start() error {
log.Errorln("storage error", err)
}
+ user.SetupUsers()
+
fileWriter.SetupFileWriterReceiverService(&handler)
if err := createInitialOfflineState(); err != nil {
@@ -62,7 +65,9 @@ func Start() error {
_yp = yp.NewYP(GetStatus)
- chat.Setup(ChatListenerImpl{})
+ if err := chat.Start(GetStatus); err != nil {
+ log.Errorln(err)
+ }
// start the rtmp server
go rtmp.Start(setStreamAsConnected, setBroadcaster)
diff --git a/core/data/accessTokens.go b/core/data/accessTokens.go
deleted file mode 100644
index 77a8bff56..000000000
--- a/core/data/accessTokens.go
+++ /dev/null
@@ -1,198 +0,0 @@
-package data
-
-import (
- "errors"
- "strings"
- "time"
-
- "github.com/owncast/owncast/models"
- log "github.com/sirupsen/logrus"
-)
-
-func createAccessTokensTable() {
- log.Traceln("Creating access_tokens table...")
-
- createTableSQL := `CREATE TABLE IF NOT EXISTS access_tokens (
- "token" string NOT NULL PRIMARY KEY,
- "name" string,
- "scopes" TEXT,
- "timestamp" DATETIME DEFAULT CURRENT_TIMESTAMP,
- "last_used" DATETIME
- );`
-
- stmt, err := _db.Prepare(createTableSQL)
- if err != nil {
- log.Fatal(err)
- }
- defer stmt.Close()
- if _, err := stmt.Exec(); err != nil {
- log.Warnln(err)
- }
-}
-
-// InsertToken will add a new token to the database.
-func InsertToken(token string, name string, scopes []string) error {
- log.Println("Adding new access token:", name)
-
- scopesString := strings.Join(scopes, ",")
-
- tx, err := _db.Begin()
- if err != nil {
- return err
- }
- stmt, err := tx.Prepare("INSERT INTO access_tokens(token, name, scopes) values(?, ?, ?)")
-
- if err != nil {
- return err
- }
- defer stmt.Close()
-
- if _, err := stmt.Exec(token, name, scopesString); err != nil {
- return err
- }
-
- if err = tx.Commit(); err != nil {
- return err
- }
-
- return nil
-}
-
-// DeleteToken will delete a token from the database.
-func DeleteToken(token string) error {
- log.Println("Deleting access token:", token)
-
- tx, err := _db.Begin()
- if err != nil {
- return err
- }
- stmt, err := tx.Prepare("DELETE FROM access_tokens WHERE token = ?")
-
- if err != nil {
- return err
- }
- defer stmt.Close()
-
- result, err := stmt.Exec(token)
- if err != nil {
- return err
- }
-
- if rowsDeleted, _ := result.RowsAffected(); rowsDeleted == 0 {
- tx.Rollback() //nolint
- return errors.New(token + " not found")
- }
-
- if err = tx.Commit(); err != nil {
- return err
- }
-
- return nil
-}
-
-// DoesTokenSupportScope will determine if a specific token has access to perform a scoped action.
-func DoesTokenSupportScope(token string, scope string) (bool, error) {
- // This will split the scopes from comma separated to individual rows
- // so we can efficiently find if a token supports a single scope.
- // This is SQLite specific, so if we ever support other database
- // backends we need to support other methods.
- var query = `SELECT count(*) FROM (
- WITH RECURSIVE split(token, scope, rest) AS (
- SELECT token, '', scopes || ',' FROM access_tokens
- UNION ALL
- SELECT token,
- substr(rest, 0, instr(rest, ',')),
- substr(rest, instr(rest, ',')+1)
- FROM split
- WHERE rest <> '')
- SELECT token, scope
- FROM split
- WHERE scope <> ''
- ORDER BY token, scope
- ) AS token WHERE token.token = ? AND token.scope = ?;`
-
- row := _db.QueryRow(query, token, scope)
-
- var count = 0
- err := row.Scan(&count)
-
- return count > 0, err
-}
-
-// GetAccessTokens will return all access tokens.
-func GetAccessTokens() ([]models.AccessToken, error) { //nolint
- tokens := make([]models.AccessToken, 0)
-
- // Get all messages sent within the past day
- var query = "SELECT * FROM access_tokens"
-
- rows, err := _db.Query(query)
- if err != nil {
- return tokens, err
- }
- defer rows.Close()
-
- for rows.Next() {
- var token string
- var name string
- var scopes string
- var timestampString string
- var lastUsedString *string
-
- if err := rows.Scan(&token, &name, &scopes, ×tampString, &lastUsedString); err != nil {
- log.Error("There is a problem reading the database.", err)
- return tokens, err
- }
-
- timestamp, err := time.Parse(time.RFC3339, timestampString)
- if err != nil {
- return tokens, err
- }
-
- var lastUsed *time.Time = nil
- if lastUsedString != nil {
- lastUsedTime, _ := time.Parse(time.RFC3339, *lastUsedString)
- lastUsed = &lastUsedTime
- }
-
- singleToken := models.AccessToken{
- Name: name,
- Token: token,
- Scopes: strings.Split(scopes, ","),
- Timestamp: timestamp,
- LastUsed: lastUsed,
- }
-
- tokens = append(tokens, singleToken)
- }
-
- if err := rows.Err(); err != nil {
- return tokens, err
- }
-
- return tokens, nil
-}
-
-// SetAccessTokenAsUsed will update the last used timestamp for a token.
-func SetAccessTokenAsUsed(token string) error {
- tx, err := _db.Begin()
- if err != nil {
- return err
- }
- stmt, err := tx.Prepare("UPDATE access_tokens SET last_used = CURRENT_TIMESTAMP WHERE token = ?")
-
- if err != nil {
- return err
- }
- defer stmt.Close()
-
- if _, err := stmt.Exec(token); err != nil {
- return err
- }
-
- if err = tx.Commit(); err != nil {
- return err
- }
-
- return nil
-}
diff --git a/core/data/config.go b/core/data/config.go
index 12f538487..0fd01cccc 100644
--- a/core/data/config.go
+++ b/core/data/config.go
@@ -539,7 +539,9 @@ func VerifySettings() error {
if err := utils.Copy(defaultLogo, filepath.Join(config.DataDirectory, "logo.svg")); err != nil {
log.Errorln("error copying default logo: ", err)
}
- SetLogoPath("logo.svg")
+ if err := SetLogoPath("logo.svg"); err != nil {
+ log.Errorln("unable to set default logo to logo.svg", err)
+ }
}
return nil
@@ -577,19 +579,25 @@ func FindHighestVideoQualityIndex(qualities []models.StreamOutputVariant) int {
return indexedQualities[0].index
}
-// GetUsernameBlocklist will return the blocked usernames as a comma separated string.
-func GetUsernameBlocklist() string {
+// GetForbiddenUsernameList will return the blocked usernames as a comma separated string.
+func GetForbiddenUsernameList() []string {
usernameString, err := _datastore.GetString(blockedUsernamesKey)
if err != nil {
- log.Traceln(blockedUsernamesKey, err)
- return ""
+ return config.DefaultForbiddenUsernames
}
- return usernameString
+ if usernameString == "" {
+ return config.DefaultForbiddenUsernames
+ }
+
+ blocklist := strings.Split(usernameString, ",")
+
+ return blocklist
}
-// SetUsernameBlocklist set the username blocklist as a comma separated string.
-func SetUsernameBlocklist(usernames string) error {
- return _datastore.SetString(blockedUsernamesKey, usernames)
+// SetForbiddenUsernameList set the username blocklist as a comma separated string.
+func SetForbiddenUsernameList(usernames []string) error {
+ usernameListString := strings.Join(usernames, ",")
+ return _datastore.SetString(blockedUsernamesKey, usernameListString)
}
diff --git a/core/data/data.go b/core/data/data.go
index b2484c30c..1048c6d41 100644
--- a/core/data/data.go
+++ b/core/data/data.go
@@ -17,7 +17,7 @@ import (
)
const (
- schemaVersion = 0
+ schemaVersion = 1
)
var _db *sql.DB
@@ -45,7 +45,13 @@ func SetupPersistence(file string) error {
}
}
- db, err := sql.Open("sqlite3", file)
+ db, err := sql.Open("sqlite3", fmt.Sprintf("file:%s", file))
+ db.SetMaxOpenConns(1)
+ _db = db
+
+ createWebhooksTable()
+ createUsersTable(db)
+
if err != nil {
return err
}
@@ -86,11 +92,6 @@ func SetupPersistence(file string) error {
}
}
- _db = db
-
- createWebhooksTable()
- createAccessTokensTable()
-
_datastore = &Datastore{}
_datastore.Setup()
@@ -106,13 +107,14 @@ func SetupPersistence(file string) error {
}
func migrateDatabase(db *sql.DB, from, to int) error {
- log.Printf("Migrating database from version %d to %d\n", from, to)
+ log.Printf("Migrating database from version %d to %d", from, to)
dbBackupFile := filepath.Join(config.BackupDirectory, fmt.Sprintf("owncast-v%d.bak", from))
utils.Backup(db, dbBackupFile)
for v := from; v < to; v++ {
switch v {
case 0:
- log.Printf("Migration step from %d to %d\n", v, v+1)
+ log.Printf("Migration step from %d to %d", v, v+1)
+ migrateToSchema1(db)
default:
panic("missing database migration step")
}
diff --git a/core/data/data_test.go b/core/data/data_test.go
index f5d277d0f..2210dd711 100644
--- a/core/data/data_test.go
+++ b/core/data/data_test.go
@@ -2,13 +2,18 @@ package data
import (
"fmt"
+ "io/ioutil"
+ "os"
"testing"
)
func TestMain(m *testing.M) {
- dbFile := "../../test/test.db"
+ dbFile, err := ioutil.TempFile(os.TempDir(), "owncast-test-db.db")
+ if err != nil {
+ panic(err)
+ }
- SetupPersistence(dbFile)
+ SetupPersistence(dbFile.Name())
m.Run()
}
diff --git a/core/data/migrations.go b/core/data/migrations.go
new file mode 100644
index 000000000..ddc12c51e
--- /dev/null
+++ b/core/data/migrations.go
@@ -0,0 +1,118 @@
+package data
+
+import (
+ "database/sql"
+ "time"
+
+ "github.com/owncast/owncast/utils"
+ log "github.com/sirupsen/logrus"
+ "github.com/teris-io/shortid"
+)
+
+func migrateToSchema1(db *sql.DB) {
+ // Since it's just a backlog of chat messages let's wipe the old messages
+ // and recreate the table.
+
+ // Drop the old messages table
+ stmt, err := db.Prepare("DROP TABLE messages")
+ if err != nil {
+ log.Fatal(err)
+ }
+ defer stmt.Close()
+ _, err = stmt.Exec()
+ if err != nil {
+ log.Warnln(err)
+ }
+
+ // Recreate it
+ createUsersTable(db)
+
+ // Migrate access tokens to become chat users
+ type oldAccessToken struct {
+ accessToken string
+ displayName string
+ scopes string
+ createdAt time.Time
+ lastUsedAt *time.Time
+ }
+
+ oldAccessTokens := make([]oldAccessToken, 0)
+
+ query := `SELECT * FROM access_tokens`
+
+ rows, err := db.Query(query)
+ if err != nil || rows.Err() != nil {
+ log.Errorln("error migrating access tokens to schema v1", err, rows.Err())
+ return
+ }
+ defer rows.Close()
+
+ for rows.Next() {
+ var token string
+ var name string
+ var scopes string
+ var timestampString string
+ var lastUsedString *string
+
+ if err := rows.Scan(&token, &name, &scopes, ×tampString, &lastUsedString); err != nil {
+ log.Error("There is a problem reading the database.", err)
+ return
+ }
+
+ timestamp, err := time.Parse(time.RFC3339, timestampString)
+ if err != nil {
+ return
+ }
+
+ var lastUsed *time.Time = nil
+ if lastUsedString != nil {
+ lastUsedTime, _ := time.Parse(time.RFC3339, *lastUsedString)
+ lastUsed = &lastUsedTime
+ }
+
+ oldToken := oldAccessToken{
+ accessToken: token,
+ displayName: name,
+ scopes: scopes,
+ createdAt: timestamp,
+ lastUsedAt: lastUsed,
+ }
+
+ oldAccessTokens = append(oldAccessTokens, oldToken)
+ }
+
+ // Recreate them as users
+ for _, token := range oldAccessTokens {
+ color := utils.GenerateRandomDisplayColor()
+ if err := insertAPIToken(db, token.accessToken, token.displayName, color, token.scopes); err != nil {
+ log.Errorln("Error migrating access token", err)
+ }
+ }
+}
+
+func insertAPIToken(db *sql.DB, token string, name string, color int, scopes string) error {
+ log.Debugln("Adding new access token:", name)
+
+ id := shortid.MustGenerate()
+
+ tx, err := db.Begin()
+ if err != nil {
+ return err
+ }
+ stmt, err := tx.Prepare("INSERT INTO users(id, access_token, display_name, display_color, scopes, type) values(?, ?, ?, ?, ?, ?)")
+
+ if err != nil {
+ return err
+ }
+ defer stmt.Close()
+
+ if _, err = stmt.Exec(id, token, name, color, scopes, "API"); err != nil {
+ return err
+ }
+
+ if err = tx.Commit(); err != nil {
+ return err
+ }
+
+ return nil
+}
diff --git a/core/data/persistence.go b/core/data/persistence.go
index de9482141..deb677769 100644
--- a/core/data/persistence.go
+++ b/core/data/persistence.go
@@ -4,6 +4,7 @@ import (
"bytes"
"database/sql"
"encoding/gob"
+ "sync"
// sqlite requires a blank import.
_ "github.com/mattn/go-sqlite3"
@@ -12,14 +13,15 @@ import (
// Datastore is the global key/value store for configuration values.
type Datastore struct {
- db *sql.DB
- cache map[string][]byte
+ DB *sql.DB
+ cache map[string][]byte
+ DbLock *sync.Mutex
}
func (ds *Datastore) warmCache() {
log.Traceln("Warming config value cache")
- res, err := ds.db.Query("SELECT key, value FROM datastore")
+ res, err := ds.DB.Query("SELECT key, value FROM datastore")
if err != nil || res.Err() != nil {
log.Errorln("error warming config cache", err, res.Err())
}
@@ -48,7 +50,7 @@ func (ds *Datastore) Get(key string) (ConfigEntry, error) {
var resultKey string
var resultValue []byte
- row := ds.db.QueryRow("SELECT key, value FROM datastore WHERE key = ? LIMIT 1", key)
+ row := ds.DB.QueryRow("SELECT key, value FROM datastore WHERE key = ? LIMIT 1", key)
if err := row.Scan(&resultKey, &resultValue); err != nil {
return ConfigEntry{}, err
}
@@ -63,36 +65,26 @@ func (ds *Datastore) Get(key string) (ConfigEntry, error) {
// Save will save the ConfigEntry to the database.
func (ds *Datastore) Save(e ConfigEntry) error {
+ ds.DbLock.Lock()
+ defer ds.DbLock.Unlock()
+
var dataGob bytes.Buffer
enc := gob.NewEncoder(&dataGob)
if err := enc.Encode(e.Value); err != nil {
return err
}
- tx, err := ds.db.Begin()
+ tx, err := ds.DB.Begin()
if err != nil {
return err
}
var stmt *sql.Stmt
- var count int
- row := ds.db.QueryRow("SELECT COUNT(*) FROM datastore WHERE key = ? LIMIT 1", e.Key)
- if err := row.Scan(&count); err != nil {
+ stmt, err = tx.Prepare("INSERT INTO datastore (key, value) VALUES(?, ?) ON CONFLICT(key) DO UPDATE SET value=excluded.value")
+ if err != nil {
return err
}
+ _, err = stmt.Exec(e.Key, dataGob.Bytes())
- if count == 0 {
- stmt, err = tx.Prepare("INSERT INTO datastore(key, value) values(?, ?)")
- if err != nil {
- return err
- }
- _, err = stmt.Exec(e.Key, dataGob.Bytes())
- } else {
- stmt, err = tx.Prepare("UPDATE datastore SET value=? WHERE key=?")
- if err != nil {
- return err
- }
- _, err = stmt.Exec(dataGob.Bytes(), e.Key)
- }
if err != nil {
return err
}
@@ -110,7 +102,8 @@ func (ds *Datastore) Save(e ConfigEntry) error {
// Setup will create the datastore table and perform initial initialization.
func (ds *Datastore) Setup() {
ds.cache = make(map[string][]byte)
- ds.db = GetDatabase()
+ ds.DB = GetDatabase()
+ ds.DbLock = &sync.Mutex{}
createTableSQL := `CREATE TABLE IF NOT EXISTS datastore (
"key" string NOT NULL PRIMARY KEY,
@@ -118,7 +111,7 @@ func (ds *Datastore) Setup() {
"timestamp" DATE DEFAULT CURRENT_TIMESTAMP NOT NULL
);`
- stmt, err := ds.db.Prepare(createTableSQL)
+ stmt, err := ds.DB.Prepare(createTableSQL)
if err != nil {
log.Fatal(err)
}
@@ -137,7 +130,7 @@ func (ds *Datastore) Setup() {
// Reset will delete all config entries in the datastore and start over.
func (ds *Datastore) Reset() {
sql := "DELETE FROM datastore"
- stmt, err := ds.db.Prepare(sql)
+ stmt, err := ds.DB.Prepare(sql)
if err != nil {
log.Fatalln(err)
}
@@ -150,3 +143,7 @@ func (ds *Datastore) Reset() {
PopulateDefaults()
}
+
+func GetDatastore() *Datastore {
+ return _datastore
+}
diff --git a/core/data/users.go b/core/data/users.go
new file mode 100644
index 000000000..3b1373cbb
--- /dev/null
+++ b/core/data/users.go
@@ -0,0 +1,37 @@
+package data
+
+import (
+ "database/sql"
+
+ log "github.com/sirupsen/logrus"
+)
+
+func createUsersTable(db *sql.DB) {
+ log.Traceln("Creating users table...")
+
+ createTableSQL := `CREATE TABLE IF NOT EXISTS users (
+ "id" TEXT,
+ "access_token" string NOT NULL,
+ "display_name" TEXT NOT NULL,
+ "display_color" NUMBER NOT NULL,
+ "created_at" TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
+ "disabled_at" TIMESTAMP,
+ "previous_names" TEXT DEFAULT '',
+ "namechanged_at" TIMESTAMP,
+ "scopes" TEXT,
+ "type" TEXT DEFAULT 'STANDARD',
+ "last_used" DATETIME DEFAULT CURRENT_TIMESTAMP,
+ PRIMARY KEY (id, access_token),
+ UNIQUE(id, access_token)
+ );CREATE INDEX index ON users (id, access_token)`
+
+ stmt, err := db.Prepare(createTableSQL)
+ if err != nil {
+ log.Fatal(err)
+ }
+ defer stmt.Close()
+ _, err = stmt.Exec()
+ if err != nil {
+ log.Warnln(err)
+ }
+}
diff --git a/core/data/webhooks.go b/core/data/webhooks.go
index 8717401fb..073c30631 100644
--- a/core/data/webhooks.go
+++ b/core/data/webhooks.go
@@ -33,7 +33,7 @@ func createWebhooksTable() {
// InsertWebhook will add a new webhook to the database.
func InsertWebhook(url string, events []models.EventType) (int, error) {
- log.Println("Adding new webhook:", url)
+ log.Traceln("Adding new webhook:", url)
eventsString := strings.Join(events, ",")
@@ -67,7 +67,7 @@ func InsertWebhook(url string, events []models.EventType) (int, error) {
// DeleteWebhook will delete a webhook from the database.
func DeleteWebhook(id int) error {
- log.Println("Deleting webhook:", id)
+ log.Traceln("Deleting webhook:", id)
tx, err := _db.Begin()
if err != nil {
@@ -86,7 +86,7 @@ func DeleteWebhook(id int) error {
}
if rowsDeleted, _ := result.RowsAffected(); rowsDeleted == 0 {
- tx.Rollback() //nolint
+ _ = tx.Rollback()
return errors.New(fmt.Sprint(id) + " not found")
}
diff --git a/core/stats.go b/core/stats.go
index 3f716ce9e..c63beaf40 100644
--- a/core/stats.go
+++ b/core/stats.go
@@ -7,7 +7,6 @@ import (
log "github.com/sirupsen/logrus"
- "github.com/owncast/owncast/core/chat"
"github.com/owncast/owncast/core/data"
"github.com/owncast/owncast/geoip"
"github.com/owncast/owncast/models"
@@ -86,22 +85,6 @@ func RemoveChatClient(clientID string) {
l.Unlock()
}
-func GetChatClients() []models.Client {
- l.RLock()
- clients := make([]models.Client, 0)
- for _, client := range _stats.ChatClients {
- chatClient := chat.GetClient(client.ClientID)
- if chatClient != nil {
- clients = append(clients, chatClient.GetViewerClientFromChatClient())
- } else {
- clients = append(clients, client)
- }
- }
- l.RUnlock()
-
- return clients
-}
-
// SetViewerIdActive sets a client as active and connected.
func SetViewerIdActive(id string) {
l.Lock()
diff --git a/core/streamState.go b/core/streamState.go
index b95fb243c..556998d20 100644
--- a/core/streamState.go
+++ b/core/streamState.go
@@ -11,6 +11,7 @@ import (
log "github.com/sirupsen/logrus"
"github.com/owncast/owncast/config"
+ "github.com/owncast/owncast/core/chat"
"github.com/owncast/owncast/core/data"
"github.com/owncast/owncast/core/rtmp"
"github.com/owncast/owncast/core/transcoder"
@@ -72,10 +73,15 @@ func setStreamAsConnected(rtmpOut *io.PipeReader) {
go webhooks.SendStreamStatusEvent(models.StreamStarted)
transcoder.StartThumbnailGenerator(segmentPath, data.FindHighestVideoQualityIndex(_currentBroadcast.OutputSettings))
+
+ _ = chat.SendSystemAction("Stay tuned, the stream is starting!", true)
+ chat.SendAllWelcomeMessage()
}
// SetStreamAsDisconnected sets the stream as disconnected.
func SetStreamAsDisconnected() {
+ _ = chat.SendSystemAction("The stream is ending.", true)
+
_stats.StreamConnected = false
_stats.LastDisconnectTime = utils.NullTime{Time: time.Now(), Valid: true}
_broadcaster = nil
diff --git a/core/user/externalAPIUser.go b/core/user/externalAPIUser.go
new file mode 100644
index 000000000..e411ca5ec
--- /dev/null
+++ b/core/user/externalAPIUser.go
@@ -0,0 +1,264 @@
+package user
+
+import (
+ "database/sql"
+ "errors"
+ "strings"
+ "time"
+
+ "github.com/owncast/owncast/utils"
+ log "github.com/sirupsen/logrus"
+ "github.com/teris-io/shortid"
+)
+
+// ExternalAPIUser represents a single 3rd party integration that uses an access token.
+// This struct mostly matches the User struct so they can be used interchangeably.
+type ExternalAPIUser struct {
+ Id string `json:"id"`
+ AccessToken string `json:"accessToken"`
+ DisplayName string `json:"displayName"`
+ DisplayColor int `json:"displayColor"`
+ CreatedAt time.Time `json:"createdAt"`
+ Scopes []string `json:"scopes"`
+ Type string `json:"type,omitempty"` // Should be API
+ LastUsedAt *time.Time `json:"lastUsedAt,omitempty"`
+}
+
+const (
+ // ScopeCanSendChatMessages will allow sending chat messages as itself.
+ ScopeCanSendChatMessages = "CAN_SEND_MESSAGES"
+ // ScopeCanSendSystemMessages will allow sending chat messages as the system.
+ ScopeCanSendSystemMessages = "CAN_SEND_SYSTEM_MESSAGES"
+ // ScopeHasAdminAccess will allow performing administrative actions on the server.
+ ScopeHasAdminAccess = "HAS_ADMIN_ACCESS"
+)
+
+// For a scope to be seen as "valid" it must live in this slice.
+var validAccessTokenScopes = []string{
+ ScopeCanSendChatMessages,
+ ScopeCanSendSystemMessages,
+ ScopeHasAdminAccess,
+}
+
+// InsertToken will add a new token to the database.
+func InsertExternalAPIUser(token string, name string, color int, scopes []string) error {
+ log.Traceln("Adding new API user:", name)
+
+ _datastore.DbLock.Lock()
+ defer _datastore.DbLock.Unlock()
+
+ scopesString := strings.Join(scopes, ",")
+ id := shortid.MustGenerate()
+
+ tx, err := _datastore.DB.Begin()
+ if err != nil {
+ return err
+ }
+ stmt, err := tx.Prepare("INSERT INTO users(id, access_token, display_name, display_color, scopes, type, previous_names) values(?, ?, ?, ?, ?, ?, ?)")
+
+ if err != nil {
+ return err
+ }
+ defer stmt.Close()
+
+ if _, err = stmt.Exec(id, token, name, color, scopesString, "API", name); err != nil {
+ return err
+ }
+
+ if err = tx.Commit(); err != nil {
+ return err
+ }
+
+ return nil
+}
+
+// DeleteExternalAPIUser will delete a token from the database.
+func DeleteExternalAPIUser(token string) error {
+ log.Traceln("Deleting access token:", token)
+
+ _datastore.DbLock.Lock()
+ defer _datastore.DbLock.Unlock()
+
+ tx, err := _datastore.DB.Begin()
+ if err != nil {
+ return err
+ }
+ stmt, err := tx.Prepare("UPDATE users SET disabled_at = ? WHERE access_token = ?")
+
+ if err != nil {
+ return err
+ }
+ defer stmt.Close()
+
+ result, err := stmt.Exec(time.Now(), token)
+ if err != nil {
+ return err
+ }
+
+ if rowsDeleted, _ := result.RowsAffected(); rowsDeleted == 0 {
+ tx.Rollback() //nolint
+ return errors.New(token + " not found")
+ }
+
+ if err = tx.Commit(); err != nil {
+ return err
+ }
+
+ return nil
+}
+
+// GetExternalAPIUserForAccessTokenAndScope will determine if a specific token has access to perform a scoped action.
+func GetExternalAPIUserForAccessTokenAndScope(token string, scope string) (*ExternalAPIUser, error) {
+ // This will split the scopes from comma separated to individual rows
+ // so we can efficiently find if a token supports a single scope.
+ // This is SQLite specific, so if we ever support other database
+ // backends we need to support other methods.
+ var query = `SELECT id, access_token, scopes, display_name, display_color, created_at, last_used FROM (
+ WITH RECURSIVE split(id, access_token, scopes, display_name, display_color, created_at, last_used, disabled_at, scope, rest) AS (
+ SELECT id, access_token, scopes, display_name, display_color, created_at, last_used, disabled_at, '', scopes || ',' FROM users
+ UNION ALL
+ SELECT id, access_token, scopes, display_name, display_color, created_at, last_used, disabled_at,
+ substr(rest, 0, instr(rest, ',')),
+ substr(rest, instr(rest, ',')+1)
+ FROM split
+ WHERE rest <> '')
+ SELECT id, access_token, scopes, display_name, display_color, created_at, last_used, disabled_at, scope
+ FROM split
+ WHERE scope <> ''
+ ORDER BY access_token, scope
+ ) AS token WHERE token.access_token = ? AND token.scope = ?`
+
+ row := _datastore.DB.QueryRow(query, token, scope)
+ integration, err := makeExternalAPIUserFromRow(row)
+
+ return integration, err
+}
+
+// GetIntegrationNameForAccessToken will return the integration name associated with a specific access token.
+func GetIntegrationNameForAccessToken(token string) *string {
+ query := "SELECT display_name FROM users WHERE access_token IS ? AND disabled_at IS NULL"
+ row := _datastore.DB.QueryRow(query, token)
+
+ var name string
+ err := row.Scan(&name)
+
+ if err != nil {
+ log.Warnln(err)
+ return nil
+ }
+
+ return &name
+}
+
+// GetExternalAPIUser will return all access tokens.
+func GetExternalAPIUser() ([]ExternalAPIUser, error) { //nolint
+ // Get all messages sent within the past day
+ var query = "SELECT id, access_token, display_name, display_color, scopes, created_at, last_used FROM users WHERE type IS 'API' AND disabled_at IS NULL"
+
+ rows, err := _datastore.DB.Query(query)
+ if err != nil {
+ return []ExternalAPIUser{}, err
+ }
+ defer rows.Close()
+
+ integrations, err := makeExternalAPIUsersFromRows(rows)
+
+ return integrations, err
+}
+
+// SetExternalAPIUserAccessTokenAsUsed will update the last used timestamp for a token.
+func SetExternalAPIUserAccessTokenAsUsed(token string) error {
+ tx, err := _datastore.DB.Begin()
+ if err != nil {
+ return err
+ }
+ stmt, err := tx.Prepare("UPDATE users SET last_used = CURRENT_TIMESTAMP WHERE access_token = ?")
+
+ if err != nil {
+ return err
+ }
+ defer stmt.Close()
+
+ if _, err := stmt.Exec(token); err != nil {
+ return err
+ }
+
+ if err = tx.Commit(); err != nil {
+ return err
+ }
+
+ return nil
+}
+
+func makeExternalAPIUserFromRow(row *sql.Row) (*ExternalAPIUser, error) {
+ var id string
+ var accessToken string
+ var displayName string
+ var displayColor int
+ var scopes string
+ var createdAt time.Time
+ var lastUsedAt *time.Time
+
+ err := row.Scan(&id, &accessToken, &scopes, &displayName, &displayColor, &createdAt, &lastUsedAt)
+ if err != nil {
+ log.Errorln(err)
+ return nil, err
+ }
+
+ integration := ExternalAPIUser{
+ Id: id,
+ AccessToken: accessToken,
+ DisplayName: displayName,
+ DisplayColor: displayColor,
+ CreatedAt: createdAt,
+ Scopes: strings.Split(scopes, ","),
+ LastUsedAt: lastUsedAt,
+ }
+
+ return &integration, nil
+}
+
+func makeExternalAPIUsersFromRows(rows *sql.Rows) ([]ExternalAPIUser, error) {
+ integrations := make([]ExternalAPIUser, 0)
+
+ for rows.Next() {
+ var id string
+ var accessToken string
+ var displayName string
+ var displayColor int
+ var scopes string
+ var createdAt time.Time
+ var lastUsedAt *time.Time
+
+ err := rows.Scan(&id, &accessToken, &displayName, &displayColor, &scopes, &createdAt, &lastUsedAt)
+ if err != nil {
+ log.Errorln(err)
+ return nil, err
+ }
+
+ integration := ExternalAPIUser{
+ Id: id,
+ AccessToken: accessToken,
+ DisplayName: displayName,
+ DisplayColor: displayColor,
+ CreatedAt: createdAt,
+ Scopes: strings.Split(scopes, ","),
+ LastUsedAt: lastUsedAt,
+ }
+ integrations = append(integrations, integration)
+ }
+
+ return integrations, nil
+}
+
+// HasValidScopes will verify that all the scopes provided are valid.
+func HasValidScopes(scopes []string) bool {
+ for _, scope := range scopes {
+ _, foundInSlice := utils.FindInSlice(validAccessTokenScopes, scope)
+ if !foundInSlice {
+ log.Errorln("Invalid scope", scope)
+ return false
+ }
+ }
+ return true
+}
diff --git a/core/user/user.go b/core/user/user.go
new file mode 100644
index 000000000..cb3e25707
--- /dev/null
+++ b/core/user/user.go
@@ -0,0 +1,261 @@
+package user
+
+import (
+ "database/sql"
+ "fmt"
+ "sort"
+ "strings"
+ "time"
+
+ "github.com/owncast/owncast/core/data"
+ "github.com/owncast/owncast/utils"
+ "github.com/teris-io/shortid"
+
+ log "github.com/sirupsen/logrus"
+)
+
+var _datastore *data.Datastore
+
+type User struct {
+ Id string `json:"id"`
+ AccessToken string `json:"-"`
+ DisplayName string `json:"displayName"`
+ DisplayColor int `json:"displayColor"`
+ CreatedAt time.Time `json:"createdAt"`
+ DisabledAt *time.Time `json:"disabledAt,omitempty"`
+ PreviousNames []string `json:"previousNames"`
+ NameChangedAt *time.Time `json:"nameChangedAt,omitempty"`
+}
+
+func (u *User) IsEnabled() bool {
+ return u.DisabledAt == nil
+}
+
+func SetupUsers() {
+ _datastore = data.GetDatastore()
+}
+
+func CreateAnonymousUser(username string) (*User, error) {
+ id := shortid.MustGenerate()
+ accessToken, err := utils.GenerateAccessToken()
+ if err != nil {
+ log.Errorln("Unable to create access token for new user")
+ return nil, err
+ }
+
+ var displayName = username
+ if displayName == "" {
+ displayName = utils.GeneratePhrase()
+ }
+
+ displayColor := utils.GenerateRandomDisplayColor()
+
+ user := &User{
+ Id: id,
+ AccessToken: accessToken,
+ DisplayName: displayName,
+ DisplayColor: displayColor,
+ CreatedAt: time.Now(),
+ }
+
+ if err := create(user); err != nil {
+ return nil, err
+ }
+
+ return user, nil
+}
+
+func ChangeUsername(userId string, username string) {
+ _datastore.DbLock.Lock()
+ defer _datastore.DbLock.Unlock()
+
+ tx, err := _datastore.DB.Begin()
+
+ if err != nil {
+ log.Debugln(err)
+ }
+ defer func() {
+ if err := tx.Rollback(); err != nil {
+ log.Debugln(err)
+ }
+ }()
+
+ stmt, err := tx.Prepare("UPDATE users SET display_name = ?, previous_names = previous_names || ?, namechanged_at = ? WHERE id = ?")
+
+ if err != nil {
+ log.Debugln(err)
+ }
+ defer stmt.Close()
+
+ _, err = stmt.Exec(username, fmt.Sprintf(",%s", username), time.Now(), userId)
+ if err != nil {
+ log.Errorln(err)
+ }
+
+ if err := tx.Commit(); err != nil {
+ log.Errorln("error changing display name of user", userId, err)
+ }
+}
+
+func create(user *User) error {
+ _datastore.DbLock.Lock()
+ defer _datastore.DbLock.Unlock()
+
+ tx, err := _datastore.DB.Begin()
+ if err != nil {
+ log.Debugln(err)
+ }
+ defer func() {
+ _ = tx.Rollback()
+ }()
+
+ stmt, err := tx.Prepare("INSERT INTO users(id, access_token, display_name, display_color, previous_names, created_at) values(?, ?, ?, ?, ?, ?)")
+
+ if err != nil {
+ log.Debugln(err)
+ }
+ defer stmt.Close()
+
+ _, err = stmt.Exec(user.Id, user.AccessToken, user.DisplayName, user.DisplayColor, user.DisplayName, user.CreatedAt)
+ if err != nil {
+ log.Errorln("error creating new user", err)
+ }
+
+ return tx.Commit()
+}
+
+func SetEnabled(userID string, enabled bool) error {
+ _datastore.DbLock.Lock()
+ defer _datastore.DbLock.Unlock()
+
+ tx, err := _datastore.DB.Begin()
+ if err != nil {
+ return err
+ }
+
+ defer tx.Rollback() //nolint
+
+ var stmt *sql.Stmt
+ if !enabled {
+ stmt, err = tx.Prepare("UPDATE users SET disabled_at=DATETIME('now', 'localtime') WHERE id IS ?")
+ } else {
+ stmt, err = tx.Prepare("UPDATE users SET disabled_at=null WHERE id IS ?")
+ }
+
+ if err != nil {
+ return err
+ }
+
+ defer stmt.Close()
+
+ if _, err := stmt.Exec(userID); err != nil {
+ return err
+ }
+
+ return tx.Commit()
+}
+
+// GetUserByToken will return a user by an access token.
+func GetUserByToken(token string) *User {
+ _datastore.DbLock.Lock()
+ defer _datastore.DbLock.Unlock()
+
+ query := "SELECT id, display_name, display_color, created_at, disabled_at, previous_names, namechanged_at FROM users WHERE access_token = ?"
+ row := _datastore.DB.QueryRow(query, token)
+
+ return getUserFromRow(row)
+}
+
+// GetUserById will return a user by a user ID.
+func GetUserById(id string) *User {
+ _datastore.DbLock.Lock()
+ defer _datastore.DbLock.Unlock()
+
+ query := "SELECT id, display_name, display_color, created_at, disabled_at, previous_names, namechanged_at FROM users WHERE id = ?"
+ row := _datastore.DB.QueryRow(query, id)
+ if row == nil {
+ log.Errorln(row)
+ return nil
+ }
+ return getUserFromRow(row)
+}
+
+// GetDisabledUsers will return back all the currently disabled users that are not API users.
+func GetDisabledUsers() []*User {
+ query := "SELECT id, display_name, display_color, created_at, disabled_at, previous_names, namechanged_at FROM users WHERE disabled_at IS NOT NULL AND type IS NOT 'API'"
+
+ rows, err := _datastore.DB.Query(query)
+ if err != nil {
+ log.Errorln(err)
+ return nil
+ }
+ defer rows.Close()
+
+ users := getUsersFromRows(rows)
+
+ sort.Slice(users, func(i, j int) bool {
+ return users[i].DisabledAt.Before(*users[j].DisabledAt)
+ })
+
+ return users
+}
+
+func getUsersFromRows(rows *sql.Rows) []*User {
+ users := make([]*User, 0)
+
+ for rows.Next() {
+ var id string
+ var displayName string
+ var displayColor int
+ var createdAt time.Time
+ var disabledAt *time.Time
+ var previousUsernames string
+ var userNameChangedAt *time.Time
+
+ if err := rows.Scan(&id, &displayName, &displayColor, &createdAt, &disabledAt, &previousUsernames, &userNameChangedAt); err != nil {
+ log.Errorln("error creating collection of users from results", err)
+ return nil
+ }
+
+ user := &User{
+ Id: id,
+ DisplayName: displayName,
+ DisplayColor: displayColor,
+ CreatedAt: createdAt,
+ DisabledAt: disabledAt,
+ PreviousNames: strings.Split(previousUsernames, ","),
+ NameChangedAt: userNameChangedAt,
+ }
+ users = append(users, user)
+ }
+
+ sort.Slice(users, func(i, j int) bool {
+ return users[i].CreatedAt.Before(users[j].CreatedAt)
+ })
+
+ return users
+}
+
+func getUserFromRow(row *sql.Row) *User {
+ var id string
+ var displayName string
+ var displayColor int
+ var createdAt time.Time
+ var disabledAt *time.Time
+ var previousUsernames string
+ var userNameChangedAt *time.Time
+
+ if err := row.Scan(&id, &displayName, &displayColor, &createdAt, &disabledAt, &previousUsernames, &userNameChangedAt); err != nil {
+ return nil
+ }
+
+ return &User{
+ Id: id,
+ DisplayName: displayName,
+ DisplayColor: displayColor,
+ CreatedAt: createdAt,
+ DisabledAt: disabledAt,
+ PreviousNames: strings.Split(previousUsernames, ","),
+ NameChangedAt: userNameChangedAt,
+ }
+}
diff --git a/core/webhooks/chat.go b/core/webhooks/chat.go
index b0c1bd6f8..464628e23 100644
--- a/core/webhooks/chat.go
+++ b/core/webhooks/chat.go
@@ -1,18 +1,19 @@
package webhooks
import (
+ "github.com/owncast/owncast/core/chat/events"
"github.com/owncast/owncast/models"
)
-func SendChatEvent(chatEvent models.ChatEvent) {
+func SendChatEvent(chatEvent *events.UserMessageEvent) {
webhookEvent := WebhookEvent{
- Type: chatEvent.MessageType,
+ Type: chatEvent.GetMessageType(),
EventData: &WebhookChatMessage{
- Author: chatEvent.Author,
+ User: chatEvent.User,
Body: chatEvent.Body,
RawBody: chatEvent.RawBody,
- ID: chatEvent.ID,
- Visible: chatEvent.Visible,
+ ID: chatEvent.Id,
+ Visible: chatEvent.HiddenAt == nil,
Timestamp: &chatEvent.Timestamp,
},
}
@@ -20,7 +21,7 @@ func SendChatEvent(chatEvent models.ChatEvent) {
SendEventToWebhooks(webhookEvent)
}
-func SendChatEventUsernameChanged(event models.NameChangeEvent) {
+func SendChatEventUsernameChanged(event events.NameChangeEvent) {
webhookEvent := WebhookEvent{
Type: models.UserNameChanged,
EventData: event,
@@ -29,7 +30,7 @@ func SendChatEventUsernameChanged(event models.NameChangeEvent) {
SendEventToWebhooks(webhookEvent)
}
-func SendChatEventUserJoined(event models.UserJoinedEvent) {
+func SendChatEventUserJoined(event events.UserJoinedEvent) {
webhookEvent := WebhookEvent{
Type: models.UserNameChanged,
EventData: event,
diff --git a/core/webhooks/webhooks.go b/core/webhooks/webhooks.go
index 377e689ef..1c07fd6b8 100644
--- a/core/webhooks/webhooks.go
+++ b/core/webhooks/webhooks.go
@@ -8,6 +8,8 @@ import (
log "github.com/sirupsen/logrus"
+ "github.com/owncast/owncast/core/user"
+
"github.com/owncast/owncast/core/data"
"github.com/owncast/owncast/models"
)
@@ -18,7 +20,7 @@ type WebhookEvent struct {
}
type WebhookChatMessage struct {
- Author string `json:"author,omitempty"`
+ User *user.User `json:"user,omitempty"`
Body string `json:"body,omitempty"`
RawBody string `json:"rawBody,omitempty"`
ID string `json:"id,omitempty"`
diff --git a/doc/api/index.html b/doc/api/index.html
index 7cfa99e93..a139d01b0 100644
--- a/doc/api/index.html
+++ b/doc/api/index.html
@@ -12,568 +12,159 @@
margin: 0;
}
- "},this.getStyleTags=function(){return e.sealed?Ko(2):e._emitSheetCSS()},this.getStyleElement=function(){var t;if(e.sealed)return Ko(2);var n=((t={})[Ho]="",t["data-styled-version"]="5.3.0",t.dangerouslySetInnerHTML={__html:e.instance.toString()},t),r=ci();return r&&(n.nonce=r),[s.createElement("style",Fo({},n,{key:"sc-0-0"}))]},this.seal=function(){e.sealed=!0},this.instance=new vi({isServer:!0}),this.sealed=!1}var t=e.prototype;return t.collectStyles=function(e){return this.sealed?Ko(2):s.createElement(Ni,{sheet:this.instance},e)},t.interleaveWithNodeStream=function(e){return Ko(3)},e}(),da=function(e){var t=s.forwardRef((function(t,n){var r=(0,s.useContext)(ta),o=e.defaultProps,i=Wi(t,r,o);return s.createElement(e,Fo({},t,{theme:i,ref:n}))}));return Do()(t,e),t.displayName="WithTheme("+Vo(e)+")",t},ha=function(){return(0,s.useContext)(ta)},ma={StyleSheet:vi,masterSheet:Ri},va=aa,ga=e.css,ya=e.createGlobalStyle,ba=e.keyframes,xa=e.ThemeProvider,wa=function(e,t,n){return function(){return ga(ua||(ua=Ao(["\n @media "," screen and (max-width: ",")"," {\n ",";\n }\n "])),t?"print, ":"",(function(t){return t.theme.breakpoints[e]}),n||"",ga.apply(void 0,arguments))}},ka=e.default;function Ea(e){return function(t){if(t.theme.extensionsHook)return t.theme.extensionsHook(e,t)}}var Sa,_a,Oa,Aa=ka.div(pa||(pa=Ao(["\n padding: 20px;\n color: red;\n"]))),Ia=function(e){Eo(n,e);var t=Oo(n);function n(e){var r;return nr(this,n),(r=t.call(this,e)).state={error:void 0},r}return or(n,[{key:"componentDidCatch",value:function(e){return this.setState({error:e}),!1}},{key:"render",value:function(){return this.state.error?s.createElement(Aa,null,s.createElement("h1",null,"Something went wrong..."),s.createElement("small",null," ",this.state.error.message," "),s.createElement("p",null,s.createElement("details",null,s.createElement("summary",null,"Stack trace"),s.createElement("pre",null,this.state.error.stack))),s.createElement("small",null," ReDoc Version: ","2.0.0-rc.55")," ",s.createElement("br",null),s.createElement("small",null," Commit: ","68690d18")):s.Children.only(this.props.children)}}]),n}(s.Component),Ca=ba(Sa||(Sa=Ao(["\n 0% {\n transform: rotate(0deg); }\n 100% {\n transform: rotate(360deg);\n }\n"]))),Ta=ka((function(e){return s.createElement("svg",{className:e.className,version:"1.1",width:"512",height:"512",viewBox:"0 0 512 512"},s.createElement("path",{d:"M275.682 147.999c0 10.864-8.837 19.661-19.682 19.661v0c-10.875 0-19.681-8.796-19.681-19.661v-96.635c0-10.885 8.806-19.661 19.681-19.661v0c10.844 0 19.682 8.776 19.682 19.661v96.635z"}),s.createElement("path",{d:"M275.682 460.615c0 10.865-8.837 19.682-19.682 19.682v0c-10.875 0-19.681-8.817-19.681-19.682v-96.604c0-10.885 8.806-19.681 19.681-19.681v0c10.844 0 19.682 8.796 19.682 19.682v96.604z"}),s.createElement("path",{d:"M147.978 236.339c10.885 0 19.681 8.755 19.681 19.641v0c0 10.885-8.796 19.702-19.681 19.702h-96.624c-10.864 0-19.661-8.817-19.661-19.702v0c0-10.885 8.796-19.641 19.661-19.641h96.624z"}),s.createElement("path",{d:"M460.615 236.339c10.865 0 19.682 8.755 19.682 19.641v0c0 10.885-8.817 19.702-19.682 19.702h-96.584c-10.885 0-19.722-8.817-19.722-19.702v0c0-10.885 8.837-19.641 19.722-19.641h96.584z"}),s.createElement("path",{d:"M193.546 165.703c7.69 7.66 7.68 20.142 0 27.822v0c-7.701 7.701-20.162 7.701-27.853 0.020l-68.311-68.322c-7.68-7.701-7.68-20.142 0-27.863v0c7.68-7.68 20.121-7.68 27.822 0l68.342 68.342z"}),s.createElement("path",{d:"M414.597 386.775c7.7 7.68 7.7 20.163 0.021 27.863v0c-7.7 7.659-20.142 7.659-27.843-0.062l-68.311-68.26c-7.68-7.7-7.68-20.204 0-27.863v0c7.68-7.7 20.163-7.7 27.842 0l68.291 68.322z"}),s.createElement("path",{d:"M165.694 318.464c7.69-7.7 20.153-7.7 27.853 0v0c7.68 7.659 7.69 20.163 0 27.863l-68.342 68.322c-7.67 7.659-20.142 7.659-27.822-0.062v0c-7.68-7.68-7.68-20.122 0-27.801l68.311-68.322z"}),s.createElement("path",{d:"M386.775 97.362c7.7-7.68 20.142-7.68 27.822 0v0c7.7 7.68 7.7 20.183 0.021 27.863l-68.322 68.311c-7.68 7.68-20.163 7.68-27.843-0.020v0c-7.68-7.68-7.68-20.162 0-27.822l68.322-68.332z"}))}))(_a||(_a=Ao(["\n animation: 2s "," linear infinite;\n width: 50px;\n height: 50px;\n content: '';\n display: inline-block;\n margin-left: -25px;\n\n path {\n fill: ",";\n }\n"])),Ca,(function(e){return e.color})),Ra=ka.div(Oa||(Oa=Ao(["\n font-family: helvetica, sans;\n width: 100%;\n text-align: center;\n font-size: 25px;\n margin: 30px 0 20px 0;\n color: ",";\n"])),(function(e){return e.color})),Pa=function(e){Eo(n,e);var t=Oo(n);function n(){return nr(this,n),t.apply(this,arguments)}return or(n,[{key:"render",value:function(){return s.createElement("div",{style:{textAlign:"center"}},s.createElement(Ra,{color:this.props.color},"Loading ..."),s.createElement(Ta,{color:this.props.color}))}}]),n}(s.PureComponent),ja=r(5697),La=s.createContext(new ko({})),Na=La.Provider,Ma=La.Consumer;function Da(e,t,n,r,o,i,a){try{var s=e[i](a),l=s.value}catch(e){return void n(e)}s.done?t(l):Promise.resolve(l).then(r,o)}function Fa(e){return function(){var t=this,n=arguments;return new Promise((function(r,o){var i=e.apply(t,n);function a(e){Da(i,r,o,a,s,"next",e)}function s(e){Da(i,r,o,a,s,"throw",e)}a(void 0)}))}}var za=r(7757),Ua=r.n(za),Ba=r(2840),$a=r(7306),qa=r(6399);function Va(e){return Wa.apply(this,arguments)}function Wa(){return(Wa=Fa(Ua().mark((function e(t){var n,o,i,a;return Ua().wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return n=new $a.De({}),o={config:n,base:Jr?window.location.href:process.cwd()},Jr&&(n.resolve.http.customFetch=r.g.fetch),"object"==typeof t&&null!==t?o.doc={source:{absoluteRef:""},parsed:t}:o.ref=t,e.next=6,(0,Ba.bundle)(o);case 6:return i=e.sent,a=i.bundle.parsed,e.abrupt("return",void 0!==a.swagger?Ha(a):a);case 9:case"end":return e.stop()}}),e)})))).apply(this,arguments)}function Ha(e){return console.warn("[ReDoc Compatibility mode]: Converting OpenAPI 2.0 to OpenAPI 3.0"),new Promise((function(t,n){return(0,qa.convertObj)(e,{patch:!0,warnOnly:!0,text:"{}",anchors:!0},(function(e,r){if(e)return n(e);t(r&&r.openapi)}))}))}function Ya(e,t,n,r,o){var i={};return Object.keys(r).forEach((function(e){i[e]=r[e]})),i.enumerable=!!i.enumerable,i.configurable=!!i.configurable,("value"in i||i.initializer)&&(i.writable=!0),i=n.slice().reverse().reduce((function(n,r){return r(e,t,n)||n}),i),o&&void 0!==i.initializer&&(i.value=i.initializer?i.initializer.call(o):void 0,i.initializer=void 0),void 0===i.initializer&&(Object.defineProperty(e,t,i),i=null),i}var Ga=r(1851),Qa=r(6729),Xa=r(3573),Ka=r.n(Xa),Za=Xa.parse,Ja=function(){function e(){nr(this,e)}return or(e,null,[{key:"baseName",value:function(t){var n=arguments.length>1&&void 0!==arguments[1]?arguments[1]:1,r=e.parse(t);return r[r.length-n]}},{key:"dirName",value:function(t){var n=arguments.length>1&&void 0!==arguments[1]?arguments[1]:1,r=e.parse(t);return Xa.compile(r.slice(0,r.length-n))}},{key:"relative",value:function(t,n){var r=e.parse(t);return e.parse(n).slice(r.length)}},{key:"parse",value:function(e){var t=e;return"#"===t.charAt(0)&&(t=t.substring(1)),Za(t)}},{key:"join",value:function(t,n){var r=e.parse(t).concat(n);return Xa.compile(r)}},{key:"get",value:function(e,t){return Xa.get(e,t)}},{key:"compile",value:function(e){return Xa.compile(e)}},{key:"escape",value:function(e){return Xa.escape(e)}}]),e}();function es(e){return function(e){if(Array.isArray(e))return to(e)}(e)||function(e){if("undefined"!=typeof Symbol&&null!=e[Symbol.iterator]||null!=e["@@iterator"])return Array.from(e)}(e)||no(e)||function(){throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}()}Xa.parse=Ja.parse,Object.assign(Ja,Xa),r(6699),r(2023),r(4723);var ts=r(6470),ns=r(3578);function rs(e){return"string"==typeof e&&/\dxx/i.test(e)}function os(e){var t=arguments.length>1&&void 0!==arguments[1]&&arguments[1];if("default"===e)return t?"error":"success";var n="string"==typeof e?parseInt(e,10):e;if(rs(e)&&(n*=100),n<100||n>599)throw new Error("invalid HTTP code");var r="success";return n>=300&&n<400?r="redirect":n>=400?r="error":n<200&&(r="info"),r}var is={get:!0,post:!0,put:!0,head:!0,patch:!0,delete:!0,options:!0,$ref:!0};function as(e){return e in is}function ss(e){return e.summary||e.operationId||e.description&&e.description.substring(0,50)||e.pathName||""}var ls={multipleOf:"number",maximum:"number",exclusiveMaximum:"number",minimum:"number",exclusiveMinimum:"number",maxLength:"string",minLength:"string",pattern:"string",items:"array",maxItems:"array",minItems:"array",uniqueItems:"array",maxProperties:"object",minProperties:"object",required:"object",additionalProperties:"object",properties:"object"};function cs(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:e.type;if(void 0!==e.oneOf||void 0!==e.anyOf)return!1;var n=!0,r=Array.isArray(t);return("object"===t||r&&null!=t&&t.includes("object"))&&(n=void 0!==e.properties?0===Object.keys(e.properties).length:void 0===e.additionalProperties),void 0!==e.items&&("array"===t||r&&null!=t&&t.includes("array"))&&(n=cs(e.items,e.items.type)),n}function us(e){return-1!==e.search(/json/i)}function ps(e,t,n){return Array.isArray(e)?e.map((function(e){return e.toString()})).join(n):"object"==typeof e?Object.keys(e).map((function(t){return"".concat(t).concat(n).concat(e[t])})).join(n):t+"="+e.toString()}function fs(e,t){return Array.isArray(e)?(console.warn("deepObject style cannot be used with array value:"+e.toString()),""):"object"==typeof e?Object.keys(e).map((function(n){return"".concat(t,"[").concat(n,"]=").concat(e[n])})).join("&"):(console.warn("deepObject style cannot be used with non-object value:"+e.toString()),"")}function ds(e,t,r){var o="__redoc_param_name__",i=t?"*":"";return ns.parse("{?".concat(o).concat(i,"}")).expand(n({},o,r)).substring(1).replace(/__redoc_param_name__/g,e)}function hs(e,t){return us(t)?JSON.stringify(e):(console.warn("Parameter serialization as ".concat(t," is not supported")),"")}function ms(e){return/^#\/components\/(schemas|pathItems)\/[^\/]+$/.test(e||"")}function vs(e){if(e){var t=e.match(/^#\/components\/(schemas|pathItems)\/([^\/]+)$/);return null===t?void 0:t[1]}}function gs(e,t,n){var r;return void 0!==t&&void 0!==n?r=t===n?"".concat(t," ").concat(e):"[ ".concat(t," .. ").concat(n," ] ").concat(e):void 0!==n?r="<= ".concat(n," ").concat(e):void 0!==t&&(r=1===t?"non-empty":">= ".concat(t," ").concat(e)),r}function ys(e){var t=[],n=gs("characters",e.minLength,e.maxLength);void 0!==n&&t.push(n);var r=gs("items",e.minItems,e.maxItems);void 0!==r&&t.push(r);var o,i=function(e){if(void 0!==e){var t=e.toString(10);return/^0\.0*1$/.test(t)?"decimal places <= ".concat(t.split(".")[1].length):"multiple of ".concat(t)}}(e.multipleOf);if(void 0!==i&&t.push(i),void 0!==e.minimum&&void 0!==e.maximum?(o=e.exclusiveMinimum?"( ":"[ ",o+=e.minimum,o+=" .. ",o+=e.maximum,o+=e.exclusiveMaximum?" )":" ]"):void 0!==e.maximum?(o=e.exclusiveMaximum?"< ":"<= ",o+=e.maximum):void 0!==e.minimum&&(o=e.exclusiveMinimum?"> ":">= ",o+=e.minimum),"number"==typeof e.exclusiveMinimum||"number"==typeof e.exclusiveMaximum){var a=0,s=0;e.minimum&&(a=e.minimum),"number"==typeof e.exclusiveMinimum&&(a=a<=e.exclusiveMinimum?a:e.exclusiveMinimum),e.maximum&&(s=e.maximum),"number"==typeof e.exclusiveMaximum&&(s=s>e.exclusiveMaximum?s:e.exclusiveMaximum),o="[".concat(a," .. ").concat(s,"]")}return void 0!==o&&t.push(o),e.uniqueItems&&t.push("unique"),t}function bs(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:[],n=[],r=[],o=[];return e.forEach((function(e){e.required?t.includes(e.name)?r.push(e):o.push(e):n.push(e)})),r.sort((function(e,n){return t.indexOf(e.name)-t.indexOf(n.name)})),[].concat(r,o,n)}function xs(e,t){return es(e).sort((function(e,n){return e[t].localeCompare(n[t])}))}function ws(e,t){var n=void 0===e?function(e){try{var t=mo(e);return t.search="",t.toString()}catch(t){return e}}(function(){if(!Jr)return"";var e=window.location.href;return e.endsWith(".html")?(0,ts.dirname)(e):e}()):(0,ts.dirname)(e);return 0===t.length&&(t=[{url:"/"}]),t.map((function(e){return a(a({},e),{},{url:(t=e.url,function(e,t){var n;if(t.startsWith("//")){var r=(0,ao.parse)(e).protocol;n="".concat(r||"https:").concat(t)}else if(function(e){return/(?:^[a-z][a-z0-9+.-]*:|\/\/)/i.test(e)}(t))n=t;else if(t.startsWith("/")){var o=(0,ao.parse)(e);n=(0,ao.format)(a(a({},o),{},{pathname:t}))}else n=co(e)+"/"+t;return co(n)}(n,t)),description:e.description||""});var t}))}var ks="security-definitions",Es="SecurityDefinitions",Ss="section/Authentication/";function _s(e){Ss=e}var Os=function(e){return{delete:"del",options:"opts"}[e]||e};function As(e,t){return Object.keys(e).filter((function(e){return!0===t?e.startsWith("x-")&&!function(e){return e in{"x-circular-ref":!0,"x-code-samples":!0,"x-codeSamples":!0,"x-displayName":!0,"x-examples":!0,"x-ignoredHeaderParameters":!0,"x-logo":!0,"x-nullable":!0,"x-servers":!0,"x-tagGroups":!0,"x-traitTag":!0,"x-additionalPropertiesName":!0,"x-explicitMappingOnly":!0}}(e):e.startsWith("x-")&&t.indexOf(e)>-1})).reduce((function(t,n){return t[n]=e[n],t}),{})}var Is=r(5660),Cs=(r(7874),r(4279),r(5433),r(6213),r(2731),r(9016),r(7046),r(57),r(2503),r(6841),r(6854),r(4335),r(1426),r(8246),r(9945),r(366),r(9385),r(2886),r(5266),r(874),"clike");function Ts(e){return{json:"js","c++":"cpp","c#":"csharp","objective-c":"objectivec",shell:"bash",viml:"vim"}[e]||Cs}function Rs(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:Cs;t=t.toLowerCase();var n=Is.languages[t];return n||(n=Is.languages[Ts(t)]),Is.highlight(e.toString(),n,t)}Is.languages.insertBefore("javascript","string",{"property string":{pattern:/([{,]\s*)"(?:\\.|[^\\"\r\n])*"(?=\s*:)/i,lookbehind:!0}},void 0),Is.languages.insertBefore("javascript","punctuation",{property:{pattern:/([{,]\s*)[a-z]\w*(?=\s*:)/i,lookbehind:!0}},void 0);var Ps,js={};function Ls(e,t,n){if("function"==typeof n.value)return function(e,t,n){if(!n.value||n.value.length>0)throw new Error("@memoize decorator can only be applied to methods of zero arguments");var r="_memoized_".concat(t),o=n.value;return e[r]=js,a(a({},n),{},{value:function(){return this[r]===js&&(this[r]=o.call(this)),this[r]}})}(e,t,n);if("function"==typeof n.get)return function(e,t,n){var r="_memoized_".concat(t),o=n.get;return e[r]=js,a(a({},n),{},{get:function(){return this[r]===js&&(this[r]=o.call(this)),this[r]}})}(e,t,n);throw new Error("@memoize decorator can be applied to methods or getters, got "+String(n.value)+" instead")}var Ns="hashchange",Ms=new(Ya((Ps=function(){function e(){var t=this;nr(this,e),n(this,"_emiter",void 0),n(this,"emit",(function(){t._emiter.emit(Ns,t.currentId)})),this._emiter=new Qa.EventEmitter,this.bind()}return or(e,[{key:"currentId",get:function(){return Jr?decodeURIComponent(window.location.hash.substring(1)):""}},{key:"linkForId",value:function(e){return e?"#"+e:""}},{key:"subscribe",value:function(e){var t=this._emiter.addListener(Ns,e);return function(){return t.removeListener(Ns,e)}}},{key:"bind",value:function(){Jr&&window.addEventListener("hashchange",this.emit,!1)}},{key:"dispose",value:function(){Jr&&window.removeEventListener("hashchange",this.emit)}},{key:"replace",value:function(e){var t=arguments.length>1&&void 0!==arguments[1]&&arguments[1];Jr&&null!=e&&e!==this.currentId&&(t?window.history.replaceState(null,"",window.location.href.split("#")[0]+this.linkForId(e)):(window.history.pushState(null,"",window.location.href.split("#")[0]+this.linkForId(e)),this.emit()))}}]),e}()).prototype,"replace",[Ga.bind,Ga.debounce],Object.getOwnPropertyDescriptor(Ps.prototype,"replace"),Ps.prototype),Ps),Ds=r(813),Fs=function(){function e(){nr(this,e),n(this,"map",new Map),n(this,"prevTerm","")}return or(e,[{key:"add",value:function(e){this.map.set(e,new Ds(e))}},{key:"delete",value:function(e){this.map.delete(e)}},{key:"addOnly",value:function(e){var t=this;this.map.forEach((function(n,r){-1===e.indexOf(r)&&(n.unmark(),t.map.delete(r))}));var n,r=ro(e);try{for(r.s();!(n=r.n()).done;){var o=n.value;this.map.has(o)||this.map.set(o,new Ds(o))}}catch(e){r.e(e)}finally{r.f()}}},{key:"clearAll",value:function(){this.unmark(),this.map.clear()}},{key:"mark",value:function(e){var t=this;(e||this.prevTerm)&&(this.map.forEach((function(n){n.unmark(),n.mark(e||t.prevTerm)})),this.prevTerm=e||this.prevTerm)}},{key:"unmark",value:function(){this.map.forEach((function(e){return e.unmark()})),this.prevTerm=""}}]),e}();function zs(e,t,n,r){n&&Object.defineProperty(e,t,{enumerable:n.enumerable,configurable:n.configurable,writable:n.writable,value:n.initializer?n.initializer.call(r):void 0})}r(9826);var Us=r(7084),Bs=new Us.Renderer;Us.setOptions({renderer:Bs,highlight:function(e,t){return Rs(e,t)}});var $s="(?:^ {0,3}\x3c!-- ReDoc-Inject:\\s+?<({component}).*?/?>\\s+?--\x3e\\s*$|(?:^ {0,3}<({component})([\\s\\S]*?)>([\\s\\S]*?)\\2>|^ {0,3}<({component})([\\s\\S]*?)(?:/>|\\n{2,})))",qs=function(){function e(t){var r=this;nr(this,e),this.options=t,n(this,"headings",[]),n(this,"currentTopHeading",void 0),n(this,"headingEnhanceRenderer",void 0),n(this,"originalHeadingRule",void 0),n(this,"headingRule",(function(e,t,n,o){return 1===t?r.currentTopHeading=r.saveHeading(e,t):2===t&&r.saveHeading(e,t,r.currentTopHeading&&r.currentTopHeading.items,r.currentTopHeading&&r.currentTopHeading.id),r.originalHeadingRule(e,t,n,o)})),this.headingEnhanceRenderer=new Us.Renderer,this.originalHeadingRule=this.headingEnhanceRenderer.heading.bind(this.headingEnhanceRenderer),this.headingEnhanceRenderer.heading=this.headingRule}return or(e,[{key:"saveHeading",value:function(e,t){var n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:this.headings,r=arguments.length>3?arguments[3]:void 0;e=vo(e);var o={id:r?"".concat(r,"/").concat(ho(e)):"section/".concat(ho(e)),name:e,level:t,items:[]};return n.push(o),o}},{key:"flattenHeadings",value:function(e){if(void 0===e)return[];var t,n=[],r=ro(e);try{for(r.s();!(t=r.n()).done;){var o=t.value;n.push(o),n.push.apply(n,es(this.flattenHeadings(o.items)))}}catch(e){r.e(e)}finally{r.f()}return n}},{key:"attachHeadingsDescriptions",value:function(e){var t=function(e){return new RegExp("##?\\s+".concat(e.name.replace(/[-\/\\^$*+?.()|[\]{}]/g,"\\$&")))},n=this.flattenHeadings(this.headings);if(!(n.length<1)){for(var r=n[0],o=t(r),i=e.search(o),a=1;a1&&void 0!==arguments[1]&&arguments[1],n=t?{renderer:this.headingEnhanceRenderer}:void 0,r=Us(e.toString(),n);return r}},{key:"extractHeadings",value:function(e){this.renderMd(e,!0),this.attachHeadingsDescriptions(e);var t=this.headings;return this.headings=[],t}},{key:"renderMdWithComponents",value:function(e){var t=this.options&&this.options.allowedMdComponents;if(!t||0===Object.keys(t).length)return[this.renderMd(e)];for(var n=Object.keys(t).join("|"),r=new RegExp($s.replace(/{component}/g,n),"mig"),o=[],i=[],s=r.exec(e),l=0;s;){o.push(e.substring(l,s.index)),l=r.lastIndex;var c=t[s[1]||s[2]||s[5]],u=s[3]||s[6],p=s[4];c&&i.push({component:c.component,propsSelector:c.propsSelector,props:a(a(a({},Vs(u)),c.props),{},{children:p})}),s=r.exec(e)}o.push(e.substring(l));for(var f=[],d=0;d-1?e.substring(0,n):e}}]),e}();function Vs(e){if(!e)return{};for(var t,n=/([\w-]+)\s*=\s*(?:{([^}]+?)}|"([^"]+?)")/gim,r={};null!==(t=n.exec(e));)if(t[3])r[t[1]]=t[3];else if(t[2]){var o=void 0;try{o=JSON.parse(t[2])}catch(e){}r[t[1]]=o}return r}var Ws,Hs,Ys,Gs,Qs=function(){function e(t){nr(this,e),this.parser=t,n(this,"title",void 0),n(this,"version",void 0),n(this,"description",void 0),n(this,"summary",void 0),n(this,"termsOfService",void 0),n(this,"contact",void 0),n(this,"license",void 0),n(this,"downloadLink",void 0),n(this,"downloadFileName",void 0),Object.assign(this,t.spec.info),this.description=t.spec.info.description||"",this.summary=t.spec.info.summary||"";var r=this.description.search(/^##?\s+/m);r>-1&&(this.description=this.description.substring(0,r)),this.downloadLink=this.getDownloadLink(),this.downloadFileName=this.getDownloadFileName()}return or(e,[{key:"getDownloadLink",value:function(){if(this.parser.specUrl)return this.parser.specUrl;if(Jr&&window.Blob&&window.URL&&window.URL.createObjectURL){var e=new Blob([JSON.stringify(this.parser.spec,null,2)],{type:"application/json"});return window.URL.createObjectURL(e)}}},{key:"getDownloadFileName",value:function(){if(!this.parser.specUrl)return"swagger.json"}}]),e}(),Xs=function e(t,r){nr(this,e),n(this,"schemes",void 0);var o=r.spec.components&&r.spec.components.securitySchemes||{};this.schemes=Object.keys(t||{}).map((function(e){var n=r.deref(o[e]),i=t[e]||[];if(n)return a(a({},n),{},{id:e,sectionId:Ss+e,scopes:i});console.warn("Non existing security scheme referenced: ".concat(e,". Skipping"))})).filter((function(e){return void 0!==e}))},Ks=(Hs=Ya((Ws=function(){function e(t,r,o,i,s){nr(this,e),zs(this,"expanded",Hs,this),n(this,"name",void 0),n(this,"operations",[]),an(this),this.name=r;var l=t.deref(o);t.exitRef(o);for(var c=0,u=Object.keys(l);c4&&void 0!==arguments[4]&&arguments[4];nr(this,e),this.options=i,n(this,"pointer",void 0),n(this,"type",void 0),n(this,"displayType",void 0),n(this,"typePrefix",""),n(this,"title",void 0),n(this,"description",void 0),n(this,"externalDocs",void 0),n(this,"isPrimitive",void 0),n(this,"isCircular",!1),n(this,"format",void 0),n(this,"displayFormat",void 0),n(this,"nullable",void 0),n(this,"deprecated",void 0),n(this,"pattern",void 0),n(this,"example",void 0),n(this,"enum",void 0),n(this,"default",void 0),n(this,"readOnly",void 0),n(this,"writeOnly",void 0),n(this,"constraints",void 0),n(this,"fields",void 0),n(this,"items",void 0),n(this,"oneOf",void 0),n(this,"oneOfType",void 0),n(this,"discriminatorProp",void 0),zs(this,"activeOneOf",Gs,this),n(this,"rawSchema",void 0),n(this,"schema",void 0),n(this,"extensions",void 0),n(this,"const",void 0),an(this),this.pointer=r.$ref||o||"",this.rawSchema=t.deref(r,!1,!0),this.schema=t.mergeAllOf(this.rawSchema,this.pointer,a),this.init(t,a),t.exitRef(r),t.exitParents(this.schema),i.showExtensions&&(this.extensions=As(this.schema,i.showExtensions))}return or(e,[{key:"activateOneOf",value:function(e){this.activeOneOf=e}},{key:"init",value:function(t,n){var r=this,o=this.schema;if(this.isCircular=o["x-circular-ref"],this.title=o.title||ms(this.pointer)&&Ja.baseName(this.pointer)||"",this.description=o.description||"",this.type=o.type||function(e){if(void 0!==e.type&&!Array.isArray(e.type))return e.type;for(var t=0,n=Object.keys(ls);t-1;return new il(e,{name:i,required:c,schema:a(a({},l),{},{default:void 0===l.default?s[i]:l.default})},n+"/properties/"+i,r)}));return r.sortPropsAlphabetically&&(l=xs(l,"name")),r.requiredPropsFirst&&(l=bs(l,r.sortPropsAlphabetically?void 0:t.required)),"object"!=typeof i&&!0!==i||l.push(new il(e,{name:("object"==typeof i&&i["x-additionalPropertiesName"]||"property name").concat("*"),required:!1,schema:!0===i?{}:i,kind:"additionalProperties"},n+"/additionalProperties",r)),l}(t,o,this.pointer,this.options);else if(("array"===this.type||Array.isArray(this.type))&&o.items&&(this.items=new e(t,o.items,this.pointer+"/items",this.options),this.displayType=this.items.displayType.split(" or ").map((function(e){return e.replace(/^(string|object|number|integer|array|boolean)s?( ?.*)/,"$1s$2")})).join(" or "),this.displayFormat=this.items.format,this.typePrefix=this.items.typePrefix+bo("arrayOf"),this.title=this.title||this.items.title,this.isPrimitive=this.items.isPrimitive,void 0===this.example&&void 0!==this.items.example&&(this.example=[this.items.example]),this.items.isPrimitive&&(this.enum=this.items.enum),Array.isArray(this.type))){var i=this.type.filter((function(e){return"array"!==e}));i.length&&(this.displayType+=" or ".concat(i.join(" or ")))}this.enum.length&&this.options.sortEnumValuesAlphabetically&&this.enum.sort()}else this.initDiscriminator(o,t)}},{key:"initOneOf",value:function(t,n){var r,o,i=this;if(this.oneOf=t.map((function(t,r){var o=n.deref(t,!1,!0),s=n.mergeAllOf(o,i.pointer+"/oneOf/"+r),l=ms(t.$ref)&&!s.title?Ja.baseName(t.$ref):"".concat(s.title||"").concat(s.const&&JSON.stringify(s.const)||""),c=new e(n,a(a({},s),{},{title:l,allOf:[a(a({},i.schema),{},{oneOf:void 0,anyOf:void 0})]}),i.pointer+"/oneOf/"+r,i.options);return n.exitRef(t),n.exitParents(s),c})),this.options.simpleOneOfTypeLabel){var s=(r=this,o=new Set,function e(t){var n,r=ro(t.oneOf||[]);try{for(r.s();!(n=r.n()).done;){var i=n.value;i.oneOf?e(i):i.type&&o.add(i.type)}}catch(e){r.e(e)}finally{r.f()}}(r),Array.from(o.values()));this.displayType=s.join(" or ")}else this.displayType=this.oneOf.map((function(e){var t=e.typePrefix+(e.title?"".concat(e.title," (").concat(e.displayType,")"):e.displayType);return t.indexOf(" or ")>-1&&(t="(".concat(t,")")),t})).join(" or ")}},{key:"initDiscriminator",value:function(t,n){var r=this,o=Js(t);this.discriminatorProp=o.propertyName;var i=n.findDerived([].concat(es(t.parentRefs||[]),[this.pointer]));if(t.oneOf){var s,l=ro(t.oneOf);try{for(l.s();!(s=l.n()).done;){var c=s.value;if(void 0!==c.$ref){var u=Ja.baseName(c.$ref);i[c.$ref]=u}}}catch(e){l.e(e)}finally{l.f()}}var p=o.mapping||{},f=o["x-explicitMappingOnly"]||!1;0===Object.keys(p).length&&(f=!1);var d={};for(var h in p){var m=p[h];Array.isArray(d[m])?d[m].push(h):d[m]=[h]}for(var v=a(f?{}:a({},i),d),g=[],y=0,b=Object.keys(v);y1&&void 0!==arguments[1]?arguments[1]:{};if(Array.isArray(e))throw new Error("Payload must have fields: "+e.toString());return Object.keys(e).map((function(n){var r=e[n],o=t[n]||{},i=o.style,a=void 0===i?"form":i,s=o.explode,l=void 0===s||s;switch(a){case"form":return ds(n,l,r);case"spaceDelimited":return ps(r,n,"%20");case"pipeDelimited":return ps(r,n,"|");case"deepObject":return fs(r,n);default:return console.warn("Incorrect or unsupported encoding style: "+a),""}})).join("&")}(this.value,i))}return or(e,[{key:"getExternalValue",value:function(e){return this.externalValueUrl?(nl[this.externalValueUrl]||(nl[this.externalValueUrl]=fetch(this.externalValueUrl).then((function(t){return t.text().then((function(n){if(!t.ok)return Promise.reject(new Error(n));if(!us(e))return n;try{return JSON.parse(n)}catch(e){return n}}))}))),nl[this.externalValueUrl]):Promise.resolve(void 0)}}]),e}(),ol={path:{style:"simple",explode:!1},query:{style:"form",explode:!0},header:{style:"simple",explode:!1},cookie:{style:"form",explode:!0}},il=(tl=Ya((el=function(){function e(t,r,o,i){var a;nr(this,e),zs(this,"expanded",tl,this),n(this,"schema",void 0),n(this,"name",void 0),n(this,"required",void 0),n(this,"description",void 0),n(this,"example",void 0),n(this,"examples",void 0),n(this,"deprecated",void 0),n(this,"in",void 0),n(this,"kind",void 0),n(this,"extensions",void 0),n(this,"explode",void 0),n(this,"style",void 0),n(this,"const",void 0),n(this,"serializationMime",void 0),an(this);var s=t.deref(r);this.kind=r.kind||"field",this.name=r.name||s.name,this.in=s.in,this.required=!!s.required;var l,c,u=s.schema,p="";if(!u&&s.in&&s.content&&(p=Object.keys(s.content)[0],u=s.content[p]&&s.content[p].schema),this.schema=new Zs(t,u||{},o,i),this.description=void 0===s.description?this.schema.description||"":s.description,this.example=s.example||this.schema.example,void 0!==s.examples&&(this.examples=lo(s.examples,(function(e,n){return new rl(t,e,n,s.encoding)}))),p)this.serializationMime=p;else if(s.style)this.style=s.style;else if(this.in){var f,d;this.style=null!==(f=null===(d=ol[this.in])||void 0===d?void 0:d.style)&&void 0!==f?f:"form"}void 0===s.explode&&this.in?this.explode=null===(l=null===(c=ol[this.in])||void 0===c?void 0:c.explode)||void 0===l||l:this.explode=!!s.explode,this.deprecated=void 0===s.deprecated?!!this.schema.deprecated:s.deprecated,t.exitRef(r),i.showExtensions&&(this.extensions=As(s,i.showExtensions)),this.const=(null===(a=this.schema)||void 0===a?void 0:a.const)||(null==s?void 0:s.const)||""}return or(e,[{key:"toggle",value:function(){this.expanded=!this.expanded}}]),e}()).prototype,"expanded",[Re],{configurable:!0,enumerable:!0,writable:!0,initializer:function(){return!1}}),Ya(el.prototype,"toggle",[Tt],Object.getOwnPropertyDescriptor(el.prototype,"toggle"),el.prototype),el);function al(e){return e<10?"0"+e:e}function sl(e,t){return t>e.length?e.repeat(Math.trunc(t/e.length)+1).substring(0,t):e}function ll(){for(var e=function(e){return e&&"object"==typeof e},t=arguments.length,n=new Array(t),r=0;rt.maxSampleDepth)return ul(hl,r),cl(fl(e));if(e.$ref){if(!n)throw new Error("Your schema contains $ref. You must provide full specification in the third parameter.");var o=decodeURIComponent(e.$ref);o.startsWith("#")&&(o=o.substring(1));var i,s=Ka().get(n,o);return!0!==dl[o]?(dl[o]=!0,i=ml(s,t,n,r),dl[o]=!1):i=cl(fl(s)),ul(hl,r),i}if(void 0!==e.example)return ul(hl,r),{value:e.example,readOnly:e.readOnly,writeOnly:e.writeOnly,type:e.type};if(void 0!==e.allOf)return ul(hl,r),function(e,t,n,r,o){var i,s=ml(e,n,r),l=[],c=ro(t);try{for(c.s();!(i=c.n()).done;){var u=i.value,p=ml(a({type:s.type},u),n,r,o),f=p.type,d=p.readOnly,h=p.writeOnly,m=p.value;s.type&&f&&f!==s.type&&(console.warn("allOf: schemas with different types can't be merged"),s.type=f),s.type=s.type||f,s.readOnly=s.readOnly||d,s.writeOnly=s.writeOnly||h,null!=m&&l.push(m)}}catch(e){c.e(e)}finally{c.f()}if("object"===s.type)return s.value=ll.apply(void 0,[s.value||{}].concat(es(l.filter((function(e){return"object"==typeof e}))))),s;"array"===s.type&&(n.quiet||console.warn('OpenAPI Sampler: found allOf with "array" type. Result may be incorrect'));var v=l[l.length-1];return s.value=null!=v?v:s.value,s}(a(a({},e),{},{allOf:void 0}),e.allOf,t,n,r);if(e.oneOf&&e.oneOf.length)return e.anyOf&&(t.quiet||console.warn("oneOf and anyOf are not supported on the same level. Skipping anyOf")),ul(hl,r),ml(e.oneOf[0],t,n,r);if(e.anyOf&&e.anyOf.length)return ul(hl,r),ml(e.anyOf[0],t,n,r);if(e.if&&e.then)return ml(ll(e.if,e.then),t,n,r);var l=null,c=null;if(void 0!==e.default)l=e.default;else if(void 0!==e.const)l=e.const;else if(void 0!==e.enum&&e.enum.length)l=e.enum[0];else if(void 0!==e.examples&&e.examples.length)l=e.examples[0];else{c=e.type,Array.isArray(c)&&e.type.length>0&&(c=e.type[0]),c||(c=fl(e));var u=xl[c];u&&(l=u(e,t,n,r))}return ul(hl,r),{value:l,readOnly:e.readOnly,writeOnly:e.writeOnly,type:c}}function vl(e){var t=0;if("boolean"==typeof e.exclusiveMinimum||"boolean"==typeof e.exclusiveMaximum){if(e.maximum&&e.minimum)return t=e.exclusiveMinimum?Math.floor(e.minimum)+1:e.minimum,(e.exclusiveMaximum&&t>=e.maximum||!e.exclusiveMaximum&&t>e.maximum)&&(t=(e.maximum+e.minimum)/2),t;if(e.minimum)return e.exclusiveMinimum?Math.floor(e.minimum)+1:e.minimum;if(e.maximum)return e.exclusiveMaximum?e.maximum>0?0:Math.floor(e.maximum)-1:e.maximum>0?0:e.maximum}else{if(e.minimum)return e.minimum;e.exclusiveMinimum?(t=Math.floor(e.exclusiveMinimum)+1)===e.exclusiveMaximum&&(t=(t+Math.floor(e.exclusiveMaximum)-1)/2):e.exclusiveMaximum?t=Math.floor(e.exclusiveMaximum)-1:e.maximum&&(t=e.maximum)}return t}function gl(e){var t=e.min,n=e.max,r=e.omitTime,o=e.omitDate,i=function(e,t,n,r){var o=n?"":e.getUTCFullYear()+"-"+al(e.getUTCMonth()+1)+"-"+al(e.getUTCDate());return t||(o+="T"+al(e.getUTCHours())+":"+al(e.getUTCMinutes())+":"+al(e.getUTCSeconds())+"Z"),o}(new Date("2019-08-24T14:15:22.123Z"),r,o);return i.lengthn&&console.warn("Using maxLength = ".concat(n,' is incorrect with format "date-time"')),i}function yl(e,t){var n=sl("string",e);return t&&n.length>t&&(n=n.substring(0,t)),n}var bl={email:function(){return"user@example.com"},"idn-email":function(){return"пошта@укр.нет"},password:function(e,t){var n="pa$$word";return e>n.length&&(n+="_",n+=sl("qwerty!@#$%^123456",e-n.length).substring(0,e-n.length)),n},"date-time":function(e,t){return gl({min:e,max:t,omitTime:!1,omitDate:!1})},date:function(e,t){return gl({min:e,max:t,omitTime:!0,omitDate:!1})},time:function(e,t){return gl({min:e,max:t,omitTime:!1,omitDate:!0}).slice(1)},ipv4:function(){return"192.168.0.1"},ipv6:function(){return"2001:0db8:85a3:0000:0000:8a2e:0370:7334"},hostname:function(){return"example.com"},"idn-hostname":function(){return"приклад.укр"},iri:function(){return"http://example.com"},"iri-reference":function(){return"../словник"},uri:function(){return"http://example.com"},"uri-reference":function(){return"../dictionary"},"uri-template":function(){return"http://example.com/{endpoint}"},uuid:function(e,t,n){return r=function(e){var t=0;if(0==e.length)return t;for(var n=0;n>>5)|0;return e=t^((n|=0)<<17|n>>>15),t=n+(r|=0)|0,n=r+o|0,((r=e+o|0)>>>0)/4294967296}}(r,r,r,r),"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,(function(e){var t=16*o()%16|0;return("x"==e?t:3&t|8).toString(16)}));var r,o},default:yl,"json-pointer":function(){return"/json/pointer"},"relative-json-pointer":function(){return"1/relative/json/pointer"},regex:function(){return"/regex/"}},xl={},wl={skipReadOnly:!1,maxSampleDepth:15};function kl(e,t,n){var r=Object.assign({},wl,t);return dl={},hl=[],ml(e,r,n).value}function El(e,t){xl[e]=t}El("array",(function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},n=arguments.length>2?arguments[2]:void 0,r=arguments.length>3?arguments[3]:void 0,o=r&&r.depth||1,i=Math.min(null!=e.maxItems?e.maxItems:1/0,e.minItems||1),a=e.items||e.contains;Array.isArray(a)&&(i=Math.max(i,a.length));var s=function(t){return Array.isArray(e.items)?a[t]||{}:a||{}},l=[];if(!a)return l;for(var c=0;c1&&void 0!==arguments[1]?arguments[1]:{},n=arguments.length>2?arguments[2]:void 0,r=arguments.length>3?arguments[3]:void 0,o={},i=r&&r.depth||1;if(e&&"object"==typeof e.properties){var a=Array.isArray(e.required)?e.required:[],s=a.reduce((function(e,t){return e[t]=!0,e}),{});Object.keys(e.properties).forEach((function(r){if(!t.skipNonRequired||s.hasOwnProperty(r)){var a=ml(e.properties[r],t,n,{propertyName:r,depth:i+1});t.skipReadOnly&&a.readOnly||t.skipWriteOnly&&a.writeOnly||(o[r]=a.value)}}))}return e&&"object"==typeof e.additionalProperties&&(o.property1=ml(e.additionalProperties,t,n,{depth:i+1}).value,o.property2=ml(e.additionalProperties,t,n,{depth:i+1}).value),o})),El("string",(function(e,t,n,r){var o=e.format||"default",i=bl[o]||yl,a=r&&r.propertyName;return i(0|e.minLength,e.maxLength,a)}));var Sl,_l,Ol,Al,Il,Cl,Tl,Rl,Pl=function(){function e(t,r,o,i,a){nr(this,e),n(this,"examples",void 0),n(this,"schema",void 0),n(this,"name",void 0),n(this,"isRequestType",void 0),n(this,"onlyRequiredInSamples",void 0),this.name=r,this.isRequestType=o,this.schema=i.schema&&new Zs(t,i.schema,"",a),this.onlyRequiredInSamples=a.onlyRequiredInSamples,void 0!==i.examples?this.examples=lo(i.examples,(function(e){return new rl(t,e,r,i.encoding)})):void 0!==i.example?this.examples={default:new rl(t,{value:t.shalowDeref(i.example)},r,i.encoding)}:us(r)&&this.generateExample(t,i)}return or(e,[{key:"generateExample",value:function(e,t){var n={skipReadOnly:this.isRequestType,skipNonRequired:this.isRequestType&&this.onlyRequiredInSamples,skipWriteOnly:!this.isRequestType,maxSampleDepth:10};if(this.schema&&this.schema.oneOf){this.examples={};var r,o=ro(this.schema.oneOf);try{for(o.s();!(r=o.n()).done;){var i=r.value,a=kl(i.rawSchema,n,e.spec);this.schema.discriminatorProp&&"object"==typeof a&&a&&(a[this.schema.discriminatorProp]=i.title),this.examples[i.title]=new rl(e,{value:a},this.name,t.encoding)}}catch(e){o.e(e)}finally{o.f()}}else this.schema&&(this.examples={default:new rl(e,{value:kl(t.schema,n,e.spec)},this.name,t.encoding)})}}]),e}(),jl=(_l=Ya((Sl=function(){function e(t,r,o,i){var s,l;nr(this,e),this.isRequestType=o,n(this,"mediaTypes",void 0),zs(this,"activeMimeIdx",_l,this),an(this),i.unstable_ignoreMimeParameters&&(s=r,l={},Object.keys(s).forEach((function(e){var t=s[e],n=e.split(";")[0].trim();l[n]?l[n]=a(a({},l[n]),t):l[n]=t})),r=l),this.mediaTypes=Object.keys(r).map((function(e){var n=r[e];return t.resetVisited(),new Pl(t,e,o,n,i)}))}return or(e,[{key:"activate",value:function(e){this.activeMimeIdx=e}},{key:"active",get:function(){return this.mediaTypes[this.activeMimeIdx]}},{key:"hasSample",get:function(){return this.mediaTypes.filter((function(e){return!!e.examples})).length>0}}]),e}()).prototype,"activeMimeIdx",[Re],{configurable:!0,enumerable:!0,writable:!0,initializer:function(){return 0}}),Ya(Sl.prototype,"activate",[Tt],Object.getOwnPropertyDescriptor(Sl.prototype,"activate"),Sl.prototype),Ya(Sl.prototype,"active",[Ne],Object.getOwnPropertyDescriptor(Sl.prototype,"active"),Sl.prototype),Sl),Ll=function e(t,r,o){nr(this,e),n(this,"description",void 0),n(this,"required",void 0),n(this,"content",void 0);var i=t.deref(r);this.description=i.description||"",this.required=!!i.required,t.exitRef(r),void 0!==i.content&&(this.content=new jl(t,i.content,!0,o))},Nl=(Al=Ya((Ol=function(){function e(t,r,o,i,s){nr(this,e),zs(this,"expanded",Al,this),n(this,"content",void 0),n(this,"code",void 0),n(this,"summary",void 0),n(this,"description",void 0),n(this,"type",void 0),n(this,"headers",[]),an(this),this.expanded="all"===s.expandResponses||s.expandResponses[r];var l=t.deref(i);t.exitRef(i),this.code=r,void 0!==l.content&&(this.content=new jl(t,l.content,!1,s)),void 0!==l["x-summary"]?(this.summary=l["x-summary"],this.description=l.description||""):(this.summary=l.description||"",this.description=""),this.type=os(r,o);var c=l.headers;void 0!==c&&(this.headers=Object.keys(c).map((function(e){var n=c[e];return new il(t,a(a({},n),{},{name:e}),"",s)})))}return or(e,[{key:"toggle",value:function(){this.expanded=!this.expanded}}]),e}()).prototype,"expanded",[Re],{configurable:!0,enumerable:!0,writable:!0,initializer:function(){return!1}}),Ya(Ol.prototype,"toggle",[Tt],Object.getOwnPropertyDescriptor(Ol.prototype,"toggle"),Ol.prototype),Ol);function Ml(e){return"payload"===e.lang&&e.requestBodyContent}var Dl,Fl,zl,Ul,Bl,$l,ql,Vl,Wl,Hl,Yl,Gl,Ql,Xl,Kl,Zl=!1,Jl=(Cl=Ya((Il=function(){function e(t,r,o,i){var a=arguments.length>4&&void 0!==arguments[4]&&arguments[4];nr(this,e),this.parser=t,this.operationSpec=r,this.options=i,n(this,"id",void 0),n(this,"absoluteIdx",void 0),n(this,"name",void 0),n(this,"description",void 0),n(this,"type","operation"),n(this,"parent",void 0),n(this,"externalDocs",void 0),n(this,"items",[]),n(this,"depth",void 0),zs(this,"ready",Cl,this),zs(this,"active",Tl,this),zs(this,"expanded",Rl,this),n(this,"pointer",void 0),n(this,"operationId",void 0),n(this,"httpVerb",void 0),n(this,"deprecated",void 0),n(this,"path",void 0),n(this,"servers",void 0),n(this,"security",void 0),n(this,"extensions",void 0),n(this,"isCallback",void 0),n(this,"isWebhook",void 0),an(this),this.pointer=r.pointer,this.description=r.description,this.parent=o,this.externalDocs=r.externalDocs,this.deprecated=!!r.deprecated,this.httpVerb=r.httpVerb,this.deprecated=!!r.deprecated,this.operationId=r.operationId,this.path=r.pathName,this.isCallback=a,this.isWebhook=!!r.isWebhook,this.name=ss(r),this.isCallback?(this.security=(r.security||[]).map((function(e){return new Xs(e,t)})),this.servers=ws("",r.servers||r.pathServers||[])):(this.id=void 0!==r.operationId?"operation/"+r.operationId:void 0!==o?o.id+this.pointer:this.pointer,this.security=(r.security||t.spec.security||[]).map((function(e){return new Xs(e,t)})),this.servers=ws(t.specUrl,r.servers||r.pathServers||t.spec.servers||[])),i.showExtensions&&(this.extensions=As(r,i.showExtensions))}return or(e,[{key:"activate",value:function(){this.active=!0}},{key:"deactivate",value:function(){this.active=!1}},{key:"toggle",value:function(){this.expanded=!this.expanded}},{key:"expand",value:function(){this.parent&&this.parent.expand()}},{key:"collapse",value:function(){}},{key:"requestBody",get:function(){return this.operationSpec.requestBody&&new Ll(this.parser,this.operationSpec.requestBody,this.options)}},{key:"codeSamples",get:function(){var e=this.operationSpec["x-codeSamples"]||this.operationSpec["x-code-samples"]||[];this.operationSpec["x-code-samples"]&&!Zl&&(Zl=!0,console.warn('"x-code-samples" is deprecated. Use "x-codeSamples" instead'));var t=this.requestBody&&this.requestBody.content;if(t&&t.hasSample){var n=Math.min(e.length,this.options.payloadSampleIdx);e=[].concat(es(e.slice(0,n)),[{lang:"payload",label:"Payload",source:"",requestBodyContent:t}],es(e.slice(n)))}return e}},{key:"parameters",get:function(){var e=this,t=function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:[],n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:[],r={};return n.forEach((function(t){t=e.shalowDeref(t),r[t.name+"_"+t.in]=!0})),(t=t.filter((function(t){return t=e.shalowDeref(t),!r[t.name+"_"+t.in]}))).concat(n)}(this.parser,this.operationSpec.pathParameters,this.operationSpec.parameters).map((function(t){return new il(e.parser,t,e.pointer,e.options)}));return this.options.sortPropsAlphabetically?xs(t,"name"):this.options.requiredPropsFirst?bs(t):t}},{key:"responses",get:function(){var e=this,t=!1;return Object.keys(this.operationSpec.responses||[]).filter((function(e){return"default"===e||("success"===os(e)&&(t=!0),"default"===(n=e)||uo(n)||rs(n));var n})).map((function(n){return new Nl(e.parser,n,t,e.operationSpec.responses[n],e.options)}))}},{key:"callbacks",get:function(){var e=this;return Object.keys(this.operationSpec.callbacks||[]).map((function(t){return new Ks(e.parser,t,e.operationSpec.callbacks[t],e.pointer,e.options)}))}}]),e}()).prototype,"ready",[Re],{configurable:!0,enumerable:!0,writable:!0,initializer:function(){return!0}}),Tl=Ya(Il.prototype,"active",[Re],{configurable:!0,enumerable:!0,writable:!0,initializer:function(){return!1}}),Rl=Ya(Il.prototype,"expanded",[Re],{configurable:!0,enumerable:!0,writable:!0,initializer:function(){return!1}}),Ya(Il.prototype,"activate",[Tt],Object.getOwnPropertyDescriptor(Il.prototype,"activate"),Il.prototype),Ya(Il.prototype,"deactivate",[Tt],Object.getOwnPropertyDescriptor(Il.prototype,"deactivate"),Il.prototype),Ya(Il.prototype,"toggle",[Tt],Object.getOwnPropertyDescriptor(Il.prototype,"toggle"),Il.prototype),Ya(Il.prototype,"requestBody",[Ls],Object.getOwnPropertyDescriptor(Il.prototype,"requestBody"),Il.prototype),Ya(Il.prototype,"codeSamples",[Ls],Object.getOwnPropertyDescriptor(Il.prototype,"codeSamples"),Il.prototype),Ya(Il.prototype,"parameters",[Ls],Object.getOwnPropertyDescriptor(Il.prototype,"parameters"),Il.prototype),Ya(Il.prototype,"responses",[Ls],Object.getOwnPropertyDescriptor(Il.prototype,"responses"),Il.prototype),Ya(Il.prototype,"callbacks",[Ls],Object.getOwnPropertyDescriptor(Il.prototype,"callbacks"),Il.prototype),Il),ec=ka.div(Dl||(Dl=Ao(["\n width: calc(100% - ",");\n padding: 0 ","px;\n\n ",";\n"])),(function(e){return e.theme.rightPanel.width}),(function(e){return e.theme.spacing.sectionHorizontal}),(function(e){var t=e.compact,n=e.theme;return wa("medium",!0)(Fl||(Fl=Ao(["\n width: 100%;\n padding: ",";\n "])),"".concat(t?0:n.spacing.sectionVertical,"px ").concat(n.spacing.sectionHorizontal,"px"))})),tc=ka.div.attrs((function(e){return n({},Uf,e.id)}))(zl||(zl=Ao(["\n padding: ","px 0;\n\n &:last-child {\n min-height: calc(100vh + 1px);\n }\n\n & > &:last-child {\n min-height: initial;\n }\n\n ","\n ","\n"])),(function(e){return e.theme.spacing.sectionVertical}),wa("medium",!0)(Ul||(Ul=Ao(["\n padding: 0;\n "]))),(function(e){return e.underlined?"\n position: relative;\n\n &:not(:last-of-type):after {\n position: absolute;\n bottom: 0;\n width: 100%;\n display: block;\n content: '';\n border-bottom: 1px solid rgba(0, 0, 0, 0.2);\n }\n ":""})),nc=ka.div(Bl||(Bl=Ao(["\n width: ",";\n color: ",";\n background-color: ",";\n padding: 0 ","px;\n\n ",";\n"])),(function(e){return e.theme.rightPanel.width}),(function(e){return e.theme.rightPanel.textColor}),(function(e){return e.theme.rightPanel.backgroundColor}),(function(e){return e.theme.spacing.sectionHorizontal}),wa("medium",!0)($l||($l=Ao(["\n width: 100%;\n padding: ",";\n "])),(function(e){return"".concat(e.theme.spacing.sectionVertical,"px ").concat(e.theme.spacing.sectionHorizontal,"px")}))),rc=ka(nc)(ql||(ql=Ao(["\n background-color: ",";\n"])),(function(e){return e.theme.rightPanel.backgroundColor})),oc=ka.div(Vl||(Vl=Ao(["\n display: flex;\n width: 100%;\n padding: 0;\n\n ",";\n"])),wa("medium",!0)(Wl||(Wl=Ao(["\n flex-direction: column;\n "])))),ic={1:"1.85714em",2:"1.57143em",3:"1.27em"},ac=function(e){return ga(Hl||(Hl=Ao(["\n font-family: ",";\n font-weight: ",";\n font-size: ",";\n line-height: ",";\n"])),(function(e){return e.theme.typography.headings.fontFamily}),(function(e){return e.theme.typography.headings.fontWeight}),ic[e],(function(e){return e.theme.typography.headings.lineHeight}))},sc=ka.h1(Yl||(Yl=Ao(["\n ",";\n color: ",";\n\n ",";\n"])),ac(1),(function(e){return e.theme.colors.primary.main}),Ea("H1")),lc=ka.h2(Gl||(Gl=Ao(["\n ",";\n color: black;\n\n ",";\n"])),ac(2),Ea("H2")),cc=(ka.h2(Ql||(Ql=Ao(["\n ",";\n color: black;\n\n ",";\n"])),ac(3),Ea("H3")),ka.h3(Xl||(Xl=Ao(["\n color: ",";\n\n ",";\n"])),(function(e){return e.theme.rightPanel.textColor}),Ea("RightPanelHeader"))),uc=ka.h5(Kl||(Kl=Ao(["\n border-bottom: 1px solid rgba(38, 50, 56, 0.3);\n margin: 1em 0 1em 0;\n color: rgba(38, 50, 56, 0.5);\n font-weight: normal;\n text-transform: uppercase;\n font-size: 0.929em;\n line-height: 20px;\n\n ",";\n"])),Ea("UnderlinedHeader"));function pc(e,t){return function(e){if(Array.isArray(e))return e}(e)||function(e,t){var n=e&&("undefined"!=typeof Symbol&&e[Symbol.iterator]||e["@@iterator"]);if(null!=n){var r,o,i=[],a=!0,s=!1;try{for(n=n.call(e);!(a=(r=n.next()).done)&&(i.push(r.value),!t||i.length!==t);a=!0);}catch(e){s=!0,o=e}finally{try{a||null==n.return||n.return()}finally{if(s)throw o}}return i}}(e,t)||no(e,t)||function(){throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}()}var fc,dc,hc=(0,s.createContext)(void 0),mc=hc.Provider,vc=hc.Consumer;function gc(e){var t=e.spec,n=e.specUrl,r=e.options,o=e.onLoaded,i=e.children,a=pc(s.useState(null),2),l=a[0],c=a[1];s.useEffect((function(){function e(){return(e=Fa(Ua().mark((function e(){var r;return Ua().wrap((function(e){for(;;)switch(e.prev=e.next){case 0:if(t||n){e.next=2;break}return e.abrupt("return",void 0);case 2:return c(null),e.next=5,Va(t||n);case 5:r=e.sent,c(r);case 7:case"end":return e.stop()}}),e)})))).apply(this,arguments)}!function(){e.apply(this,arguments)}()}),[t,n]);var u=s.useMemo((function(){if(!l)return null;try{return new pv(l,n,r)}catch(e){throw o&&o(e),e}}),[l,n,r]);return s.useEffect((function(){u&&o&&o()}),[u,o]),i({loading:!u,store:u})}var yc=function(e){return ga(fc||(fc=Ao(["\n "," {\n cursor: pointer;\n margin-left: -20px;\n padding: 0;\n line-height: 1;\n width: 20px;\n display: inline-block;\n outline: 0;\n }\n ",":before {\n content: '';\n width: 15px;\n height: 15px;\n background-size: contain;\n background-image: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZlcnNpb249IjEuMSIgeD0iMCIgeT0iMCIgd2lkdGg9IjUxMiIgaGVpZ2h0PSI1MTIiIHZpZXdCb3g9IjAgMCA1MTIgNTEyIiBlbmFibGUtYmFja2dyb3VuZD0ibmV3IDAgMCA1MTIgNTEyIiB4bWw6c3BhY2U9InByZXNlcnZlIj48cGF0aCBmaWxsPSIjMDEwMTAxIiBkPSJNNDU5LjcgMjMzLjRsLTkwLjUgOTAuNWMtNTAgNTAtMTMxIDUwLTE4MSAwIC03LjktNy44LTE0LTE2LjctMTkuNC0yNS44bDQyLjEtNDIuMWMyLTIgNC41LTMuMiA2LjgtNC41IDIuOSA5LjkgOCAxOS4zIDE1LjggMjcuMiAyNSAyNSA2NS42IDI0LjkgOTAuNSAwbDkwLjUtOTAuNWMyNS0yNSAyNS02NS42IDAtOTAuNSAtMjQuOS0yNS02NS41LTI1LTkwLjUgMGwtMzIuMiAzMi4yYy0yNi4xLTEwLjItNTQuMi0xMi45LTgxLjYtOC45bDY4LjYtNjguNmM1MC01MCAxMzEtNTAgMTgxIDBDNTA5LjYgMTAyLjMgNTA5LjYgMTgzLjQgNDU5LjcgMjMzLjR6TTIyMC4zIDM4Mi4ybC0zMi4yIDMyLjJjLTI1IDI0LjktNjUuNiAyNC45LTkwLjUgMCAtMjUtMjUtMjUtNjUuNiAwLTkwLjVsOTAuNS05MC41YzI1LTI1IDY1LjUtMjUgOTAuNSAwIDcuOCA3LjggMTIuOSAxNy4yIDE1LjggMjcuMSAyLjQtMS40IDQuOC0yLjUgNi44LTQuNWw0Mi4xLTQyYy01LjQtOS4yLTExLjYtMTgtMTkuNC0yNS44IC01MC01MC0xMzEtNTAtMTgxIDBsLTkwLjUgOTAuNWMtNTAgNTAtNTAgMTMxIDAgMTgxIDUwIDUwIDEzMSA1MCAxODEgMGw2OC42LTY4LjZDMjc0LjYgMzk1LjEgMjQ2LjQgMzkyLjMgMjIwLjMgMzgyLjJ6Ii8+PC9zdmc+Cg==');\n opacity: 0.5;\n visibility: hidden;\n display: inline-block;\n vertical-align: middle;\n }\n\n h1:hover > ","::before, h2:hover > ","::before, ",":hover::before {\n visibility: visible;\n }\n"])),e,e,e,e,e)};function bc(e){var t=s.useContext(hc),n=s.useCallback((function(n){t&&function(e,t,n){t.defaultPrevented||0!==t.button||function(e){return!!(e.metaKey||e.altKey||e.ctrlKey||e.shiftKey)}(t)||(t.preventDefault(),e.replace(n))}(t.menu.history,n,e.to)}),[t]);return t?s.createElement("a",{className:e.className,href:t.menu.history.linkForId(e.to),onClick:n,"aria-label":e.to},e.children):null}var xc,wc,kc=ka(bc)(dc||(dc=Ao(["\n ",";\n"])),yc("&"));function Ec(e){return s.createElement(kc,{to:e.to})}var Sc,_c,Oc,Ac,Ic,Cc,Tc,Rc,Pc,jc,Lc,Nc,Mc,Dc,Fc,zc,Uc,Bc,$c,qc,Vc,Wc={left:"90deg",right:"-90deg",up:"-180deg",down:"0"},Hc=ka(function(e){Eo(n,e);var t=Oo(n);function n(){return nr(this,n),t.apply(this,arguments)}return or(n,[{key:"render",value:function(){return s.createElement("svg",{className:this.props.className,style:this.props.style,version:"1.1",viewBox:"0 0 24 24",x:"0",xmlns:"http://www.w3.org/2000/svg",y:"0","aria-hidden":"true"},s.createElement("polygon",{points:"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 "}))}}]),n}(s.PureComponent))(xc||(xc=Ao(["\n height: ",";\n width: ",";\n vertical-align: middle;\n float: ",";\n transition: transform 0.2s ease-out;\n transform: rotateZ(",");\n\n polygon {\n fill: ",";\n }\n"])),(function(e){return e.size||"18px"}),(function(e){return e.size||"18px"}),(function(e){return e.float||""}),(function(e){return Wc[e.direction||"down"]}),(function(e){var t=e.color,n=e.theme;return t&&n.colors.responses[t]&&n.colors.responses[t].color||t})),Yc=ka.span(wc||(wc=Ao(["\n display: inline-block;\n padding: 2px 8px;\n margin: 0;\n background-color: ",";\n color: ",";\n font-size: ",";\n vertical-align: middle;\n line-height: 1.6;\n border-radius: 4px;\n font-weight: ",";\n font-size: 12px;\n + span[type] {\n margin-left: 4px;\n }\n"])),(function(e){return e.theme.colors[e.type].main}),(function(e){return e.theme.colors[e.type].contrastText}),(function(e){return e.theme.typography.code.fontSize}),(function(e){return e.theme.typography.fontWeightBold})),Gc=ga(Sc||(Sc=Ao(["\n text-decoration: line-through;\n color: #707070;\n"]))),Qc=ka.caption(_c||(_c=Ao(["\n text-align: right;\n font-size: 0.9em;\n font-weight: normal;\n color: ",";\n"])),(function(e){return e.theme.colors.text.secondary})),Xc=ka.td(Oc||(Oc=Ao(["\n border-left: 1px solid ",";\n box-sizing: border-box;\n position: relative;\n padding: 10px 10px 10px 0;\n\n ","\n\n tr:first-of-type > &,\n tr.last > & {\n border-left-width: 0;\n background-position: top left;\n background-repeat: no-repeat;\n background-size: 1px 100%;\n }\n\n tr:first-of-type > & {\n background-image: linear-gradient(\n to bottom,\n transparent 0%,\n transparent 22px,\n "," 22px,\n "," 100%\n );\n }\n\n tr.last > & {\n background-image: linear-gradient(\n to bottom,\n "," 0%,\n "," 22px,\n transparent 22px,\n transparent 100%\n );\n }\n\n tr.last + tr > & {\n border-left-color: transparent;\n }\n\n tr.last:first-child > & {\n background: none;\n border-left-color: transparent;\n }\n"])),(function(e){return e.theme.schema.linesColor}),wa("small")(Ac||(Ac=Ao(["\n display: block;\n overflow: hidden;\n "]))),(function(e){return e.theme.schema.linesColor}),(function(e){return e.theme.schema.linesColor}),(function(e){return e.theme.schema.linesColor}),(function(e){return e.theme.schema.linesColor})),Kc=ka(Xc)(Ic||(Ic=Ao(["\n padding: 0;\n"]))),Zc=ka(Xc)(Cc||(Cc=Ao(["\n vertical-align: top;\n line-height: 20px;\n white-space: nowrap;\n font-size: 13px;\n font-family: ",";\n\n &.deprecated {\n ",";\n }\n\n ",";\n\n ",";\n"])),(function(e){return e.theme.typography.code.fontFamily}),Gc,(function(e){return"field"!==e.kind?"font-style: italic":""}),Ea("PropertyNameCell")),Jc=ka.td(Tc||(Tc=Ao(["\n border-bottom: 1px solid #9fb4be;\n padding: 10px 0;\n width: ",";\n box-sizing: border-box;\n\n tr.expanded & {\n border-bottom: none;\n }\n\n ","\n\n ",";\n"])),(function(e){return e.theme.schema.defaultDetailsWidth}),wa("small")(Rc||(Rc=Ao(["\n padding: 0 20px;\n border-bottom: none;\n border-left: 1px solid ",";\n\n tr.last > & {\n border-left: none;\n }\n "])),(function(e){return e.theme.schema.linesColor})),Ea("PropertyDetailsCell")),eu=ka.span(Pc||(Pc=Ao(["\n color: ",";\n font-family: ",";\n margin-right: 10px;\n\n &::before {\n content: '';\n display: inline-block;\n vertical-align: middle;\n width: 10px;\n height: 1px;\n background: ",";\n }\n\n &::after {\n content: '';\n display: inline-block;\n vertical-align: middle;\n width: 1px;\n background: ",";\n height: 7px;\n }\n"])),(function(e){return e.theme.schema.linesColor}),(function(e){return e.theme.typography.code.fontFamily}),(function(e){return e.theme.schema.linesColor}),(function(e){return e.theme.schema.linesColor})),tu=ka.div(jc||(jc=Ao(["\n padding: ",";\n"])),(function(e){return e.theme.schema.nestingSpacing})),nu=ka.table(Lc||(Lc=Ao(["\n border-collapse: separate;\n border-radius: 3px;\n font-size: ",";\n\n border-spacing: 0;\n width: 100%;\n\n > tr {\n vertical-align: middle;\n }\n\n ","\n\n ","\n\n &\n ",",\n &\n ","\n ","\n ",",\n &\n ","\n ","\n ","\n ","\n "," {\n margin: ",";\n margin-right: 0;\n background: ",";\n }\n\n &\n ","\n ",",\n &\n ","\n ","\n ","\n ",",\n &\n ","\n ","\n ","\n ","\n ","\n "," {\n background: #ffffff;\n }\n"])),(function(e){return e.theme.typography.fontSize}),wa("small")(Nc||(Nc=Ao(["\n display: block;\n > tr, > tbody > tr {\n display: block;\n }\n "]))),wa("small",!1," and (-ms-high-contrast:none)")(Mc||(Mc=Ao(["\n td {\n float: left;\n width: 100%;\n }\n "]))),tu,tu,tu,tu,tu,tu,tu,tu,tu,(function(e){return e.theme.schema.nestingSpacing}),(function(e){return e.theme.schema.nestedBackground}),tu,tu,tu,tu,tu,tu,tu,tu,tu,tu,tu,tu),ru=ka.div(Dc||(Dc=Ao(["\n margin: 0 0 3px 0;\n display: inline-block;\n"]))),ou=ka.span(Fc||(Fc=Ao(["\n font-size: 0.9em;\n margin-right: 10px;\n color: ",";\n font-family: ",";\n}\n"])),(function(e){return e.theme.colors.primary.main}),(function(e){return e.theme.typography.headings.fontFamily})),iu=ka.button(zc||(zc=Ao(["\n display: inline-block;\n margin-right: 10px;\n margin-bottom: 5px;\n font-size: 0.8em;\n cursor: pointer;\n border: 1px solid ",";\n padding: 2px 10px;\n line-height: 1.5em;\n outline: none;\n &:focus {\n box-shadow: 0 0 0 1px ",";\n }\n\n ","\n"])),(function(e){return e.theme.colors.primary.main}),(function(e){return e.theme.colors.primary.main}),(function(e){return e.active?"\n color: white;\n background-color: ".concat(e.theme.colors.primary.main,";\n &:focus {\n box-shadow: none;\n background-color: ").concat(zr(.15,e.theme.colors.primary.main),";\n }\n "):"\n color: ".concat(e.theme.colors.primary.main,";\n background-color: white;\n ")})),au=ka.div(Uc||(Uc=Ao(["\n font-size: 0.9em;\n font-family: ",";\n &::after {\n content: ' [';\n }\n"])),(function(e){return e.theme.typography.code.fontFamily})),su=ka.div(Bc||(Bc=Ao(["\n font-size: 0.9em;\n font-family: ",";\n &::after {\n content: ']';\n }\n"])),(function(e){return e.theme.typography.code.fontFamily})),lu=function(){return(lu=Object.assign||function(e){for(var t,n=1,r=arguments.length;n=0&&n.push(s)}else a.value.toLowerCase().includes(r)&&n.push(a)}return n}(t,o):t}),[t,o]),l=(0,s.useCallback)((function(t,n){void 0===n&&(n=!0),i(t),n&&e(0)}),[e,i]);return{searchTerm:o,setSearchTerm:l,filteredOptions:a}},Bu=0,$u=function(e){var t,n=e.arrowRenderer,r=e.contentClassName,o=e.className,i=e.disabled,a=e.hideArrow,l=e.id,c=e.optionItemRenderer,u=e.pageKeyTraverseSize,p=e.placeholder,f=e.searchable,d=e.value,h=e.selectedValueClassName,m=(0,s.useMemo)((function(){return t||(e=Bu,Bu+=1,"react_dropdown_aria_"+e);var e}),[t=l]),v=function(e,t){var n=e.options,r=e.searchable,o=e.onChange,i=e.disabled,a=e.ariaDescribedBy,l=e.ariaLabel,c=e.ariaLabelledBy,u=e.value,p=e.defaultOpen,f=(0,s.useState)(0),d=f[0],h=f[1],m=(0,s.useState)(p),v=m[0],g=m[1],y=(0,s.useRef)(null),b=(0,s.useRef)(null),x=(0,s.useRef)(null),w=(0,s.useState)(!1),k=w[0],E=w[1],S=Uu(h,n,r),_=S.searchTerm,O=S.setSearchTerm,A=S.filteredOptions,I=(0,s.useMemo)((function(){return A.reduce(gu,[])}),[A]),C=(0,s.useMemo)((function(){return I.map((function(e){return e.value})).indexOf(u)}),[I,u]),T=(0,s.useCallback)((function(e){void 0===e&&(e=!1),O("",!1),g(!1),e&&b.current&&b.current.focus()}),[b.current,O,g]),R=(0,s.useCallback)((function(){h(C>0?C:0),g(!0)}),[g,h,C]),P=(0,s.useCallback)((function(e,t){void 0===t&&(t=!1),e&&(o(e),O("",!1)),t&&T(!0)}),[o,T,O]);!function(e,t){var n=function(n){t.current&&!t.current.contains(n.target)&&e()};(0,s.useEffect)((function(){return document.addEventListener("mouseup",n,!1),document.addEventListener("touchend",n,!1),function(){document.removeEventListener("mouseup",n),document.removeEventListener("touchend",n)}}),[])}(T,y),function(e,t){(0,s.useEffect)((function(){if(t.current&&e>=0){var n=t.current.getElementsByClassName("dropdown-option"),r=n&&n.length?n[e]:null;if(r&&r.getBoundingClientRect){var o=r.getBoundingClientRect().height,i=t.current.getBoundingClientRect().height,a=t.current.scrollTop,s=r.offsetTop<=a;r.offsetTop>=a&&r.offsetTop+o<=a+i||(s?t.current.scrollTo({top:r.offsetTop}):t.current.scrollTo({top:r.offsetTop-i+o+8}))}}}),[e])}(d,x);var j=(0,s.useMemo)((function(){return{"aria-hidden":i,"aria-expanded":v,"aria-haspopup":"listbox","aria-activedescendant":t+"_list_"+d,"aria-controls":t+"_list","aria-label":l,"aria-labelledby":c,"aria-describedby":a}}),[i,v,t,d,l,c,a]);return{focusedIndex:d,setFocusedIndex:h,open:v,setOpen:g,searchTerm:_,setSearchTerm:O,dropdownFocused:k,setDropdownFocused:E,setValue:P,filteredOptions:A,openDropdown:R,closeDropdown:T,flattenedOptions:I,container:y,inputRef:b,listWrapper:x,ariaProps:j,ariaList:zu(I,C,t)}}(e,m),g=v.open,y=v.dropdownFocused,b=v.focusedIndex,x=v.setFocusedIndex,w=v.setDropdownFocused,k=v.setValue,E=v.openDropdown,S=v.closeDropdown,_=v.searchTerm,O=v.setSearchTerm,A=v.filteredOptions,I=v.flattenedOptions,C=v.container,T=v.inputRef,R=v.listWrapper,P=v.ariaProps,j=v.ariaList,L=(0,s.useCallback)((function(){T.current&&T.current.focus()}),[T.current]),N=(0,s.useCallback)((function(){L(),i||g&&f||(g?S(!0):E())}),[g,i,f,S,E]),M=(0,s.useCallback)((function(e){switch(e){case qc.UP_ARROW:x((function(e){return 0===e?I.length-1:e-1}));break;case qc.DOWN_ARROW:x((function(e){return(e+1)%I.length}));break;case qc.PAGE_UP:x((function(e){return e-u<0&&0!==e?0:e-u<0?I.length-1:e-u}));break;case qc.PAGE_DOWN:x((function(e){return e===I.length-1?0:e+u>I.length-1?I.length-1:(e+u)%I.length}));break;case qc.ESCAPE:S(!0)}}),[x,I,u,S]),D=(0,s.useCallback)((function(e){var t=e.keyCode;-1!==cu.indexOf(t)?(e.preventDefault(),e.stopPropagation(),M(t)):t!==qc.ENTER&&(t!==qc.SPACE||f)||g?t!==qc.TAB||f?(t===qc.TAB||t===qc.ENTER)&&I.length>0&&b>=0&&g&&(e.stopPropagation(),e.preventDefault(),k(I[b],!0)):S():(e.preventDefault(),E())}),[I,k,b,g,M,E,f,S]),F=(0,s.useCallback)((function(e){O(e.target.value)}),[O]),z=(0,s.useCallback)((function(){return w(!0)}),[w]),U=(0,s.useCallback)((function(){return w(!1)}),[w]),B=(0,s.useMemo)((function(){if(a)return null;if(n)return(0,s.createElement)(Ou,{className:"dropdown-arrow"},n(g));var e=g&&f;return(0,s.createElement)(Ou,{className:"dropdown-arrow"},e&&(0,s.createElement)(Mu,null),!e&&(0,s.createElement)(Nu,null))}),[g,n,f,a]);return(0,s.createElement)(xu,{ref:C,onFocus:L,onClick:N,role:"button",width:e.width,height:e.height,disabled:i,open:g,dropdownFocused:y,className:bu("dropdown",o)},(0,s.createElement)(wu,{className:"dropdown-selector",open:g,searchable:e.searchable},(0,s.createElement)(ku,{className:"dropdown-selector-search"},(0,s.createElement)("input",lu({id:m,ref:T,value:_,onChange:F,onKeyDown:D,onFocus:z,onBlur:U,readOnly:!g||!f,disabled:i,autoComplete:"off",role:"combobox"},P))),!d&&!_&&(0,s.createElement)(_u,{className:"dropdown-selector-placeholder",centerText:e.centerText},p),d&&!_&&(0,s.createElement)(Su,{className:bu("dropdown-selector-value",h),centerText:e.centerText,value:d,open:g},d),B),j,(0,s.createElement)(Au,{maxContentHeight:e.maxContentHeight,openUp:e.openUp,open:g,className:bu("dropdown-selector-content",r),ref:R},(0,s.createElement)(Du,{selectedOption:d,options:A,focusedIndex:b,onOptionClicked:k,optionItemRenderer:c,empty:0===I.length})))};$u.defaultProps={ariaDescribedBy:null,ariaLabel:null,ariaLabelledBy:null,arrowRenderer:void 0,centerText:!1,className:void 0,contentClassName:null,defaultOpen:!1,disabled:!1,height:null,hideArrow:!1,id:null,maxContentHeight:150,openUp:!1,optionItemRenderer:void 0,pageKeyTraverseSize:10,placeholder:"Select ...",searchable:!1,selectedValueClassName:null,style:{},value:void 0,width:null};var qu,Vu,Wu,Hu=ka($u)(qu||(qu=Ao(["\n && {\n box-sizing: border-box;\n min-width: 100px;\n outline: none;\n display: inline-block;\n border-radius: 2px;\n border: 1px solid rgba(38, 50, 56, 0.5);\n vertical-align: bottom;\n padding: 2px 0px 2px 6px;\n position: relative;\n width: auto;\n background: white;\n color: #263238;\n font-family: ",";\n font-size: 0.929em;\n line-height: 1.5em;\n cursor: pointer;\n transition: border 0.25s ease, color 0.25s ease, box-shadow 0.25s ease;\n &:hover,\n &:focus-within {\n border: 1px solid ",";\n color: ",";\n box-shadow: 0px 0px 0px 1px ",";\n }\n .dropdown-selector {\n display: inline-flex;\n padding: 0;\n height: auto;\n padding-right: 20px;\n position: relative;\n margin-bottom: 5px;\n }\n .dropdown-selector-value {\n font-family: ",";\n position: relative;\n font-size: 0.929em;\n width: 100%;\n line-height: 1;\n vertical-align: middle;\n color: #263238;\n left: 0;\n transition: color 0.25s ease, text-shadow 0.25s ease;\n }\n .dropdown-arrow {\n position: absolute;\n right: 3px;\n top: 50%;\n transform: translateY(-50%);\n border-color: "," transparent transparent;\n border-style: solid;\n border-width: 0.35em 0.35em 0;\n width: 0;\n svg {\n display: none;\n }\n }\n\n .dropdown-selector-content {\n position: absolute;\n margin-top: 2px;\n left: -2px;\n right: 0;\n\n z-index: 10;\n min-width: 100px;\n\n background: white;\n border: 1px solid rgba(38, 50, 56, 0.2);\n box-shadow: 0px 2px 4px 0px rgba(34, 36, 38, 0.12), 0px 2px 10px 0px rgba(34, 36, 38, 0.08);\n\n max-height: 220px;\n overflow: auto;\n }\n\n .dropdown-option {\n font-size: 0.9em;\n color: #263238;\n cursor: pointer;\n padding: 0.4em;\n background-color: #ffffff;\n\n &[aria-selected='true'] {\n background-color: rgba(0, 0, 0, 0.05);\n }\n\n &:hover {\n background-color: rgba(38, 50, 56, 0.12);\n }\n }\n input {\n cursor: pointer;\n height: 1px;\n background-color: transparent;\n }\n }\n"])),(function(e){return e.theme.typography.headings.fontFamily}),(function(e){return e.theme.colors.primary.main}),(function(e){return e.theme.colors.primary.main}),(function(e){return e.theme.colors.primary.main}),(function(e){return e.theme.typography.headings.fontFamily}),(function(e){return e.theme.colors.primary.main})),Yu=ka(Hu)(Vu||(Vu=Ao(["\n && {\n margin-left: 10px;\n text-transform: none;\n font-size: 0.969em;\n\n font-size: 1em;\n border: none;\n padding: 0 1.2em 0 0;\n background: transparent;\n\n &:hover,\n &:focus-within {\n border: none;\n box-shadow: none;\n .dropdown-selector-value {\n color: ",";\n text-shadow: 0px 0px 0px ",";\n }\n }\n }\n"])),(function(e){return e.theme.colors.primary.main}),(function(e){return e.theme.colors.primary.main})),Gu=ka.span(Wu||(Wu=Ao(["\n margin-left: 10px;\n text-transform: none;\n font-size: 0.929em;\n color: black;\n"])));function Qu(e){return function(t){return!!t.type&&t.type.tabsRole===e}}var Xu=Qu("Tab"),Ku=Qu("TabList"),Zu=Qu("TabPanel");function Ju(){return(Ju=Object.assign||function(e){for(var t=1;t=this.getTabsCount())){var n=this.props;(0,n.onSelect)(e,n.selectedIndex,t)}},o.getNextTab=function(e){for(var t=this.getTabsCount(),n=e+1;ne;)if(!fp(this.getTab(t)))return t;return e},o.getFirstTab=function(){for(var e=this.getTabsCount(),t=0;t=0||(o[n]=e[n]);return o}(t,["children","className","disabledTabClassName","domRef","focus","forceRenderTabPanel","onSelect","selectedIndex","selectedTabClassName","selectedTabPanelClassName","environment","disableUpDownKeys"]));return s.createElement("div",lp({},o,{className:rp(n),onClick:this.handleClick,onKeyDown:this.handleKeyDown,ref:function(t){e.node=t,r&&r(t)},"data-tabs":!0}),this.getChildren())},r}(s.Component);function hp(e,t){return(hp=Object.setPrototypeOf||function(e,t){return e.__proto__=t,e})(e,t)}dp.defaultProps={className:"react-tabs",focus:!1},dp.propTypes={};var mp=function(e){var t,n;function r(t){var n;return(n=e.call(this,t)||this).handleSelected=function(e,t,r){var o=n.props.onSelect,i=n.state.mode;if("function"!=typeof o||!1!==o(e,t,r)){var a={focus:"keydown"===r.type};1===i&&(a.selectedIndex=e),n.setState(a)}},n.state=r.copyPropsToState(n.props,{},t.defaultFocus),n}return n=e,(t=r).prototype=Object.create(n.prototype),t.prototype.constructor=t,hp(t,n),r.getDerivedStateFromProps=function(e,t){return r.copyPropsToState(e,t)},r.getModeFromProps=function(e){return null===e.selectedIndex?1:0},r.copyPropsToState=function(e,t,n){void 0===n&&(n=!1);var o={focus:n,mode:r.getModeFromProps(e)};if(1===o.mode){var i,a=Math.max(0,sp(e.children)-1);i=null!=t.selectedIndex?Math.min(t.selectedIndex,a):e.defaultIndex||0,o.selectedIndex=i}return o},r.prototype.render=function(){var e=this.props,t=e.children,n=(e.defaultIndex,e.defaultFocus,function(e,t){if(null==e)return{};var n,r,o={},i=Object.keys(e);for(r=0;r=0||(o[n]=e[n]);return o}(e,["children","defaultIndex","defaultFocus"])),r=this.state,o=r.focus,i=r.selectedIndex;return n.focus=o,n.onSelect=this.handleSelected,null!=i&&(n.selectedIndex=i),s.createElement(dp,n,t)},r}(s.Component);function vp(){return(vp=Object.assign||function(e){for(var t=1;t=0||(o[n]=e[n]);return o}(e,["children","className"]);return s.createElement("ul",vp({},r,{className:rp(n),role:"tablist"}),t)},r}(s.Component);function bp(){return(bp=Object.assign||function(e){for(var t=1;t=0||(o[n]=e[n]);return o}(n,["children","className","disabled","disabledClassName","focus","id","panelId","selected","selectedClassName","tabIndex","tabRef"]);return s.createElement("li",bp({},h,{className:rp(o,(e={},e[p]=u,e[a]=i,e)),ref:function(e){t.node=e,d&&d(e)},role:"tab",id:l,"aria-selected":u?"true":"false","aria-disabled":i?"true":"false","aria-controls":c,tabIndex:f||(u?"0":null)}),r)},r}(s.Component);function Ep(){return(Ep=Object.assign||function(e){for(var t=1;t=0||(o[n]=e[n]);return o}(t,["children","className","forceRender","id","selected","selectedClassName","tabId"]);return s.createElement("div",Ep({},u,{className:rp(r,(e={},e[l]=a,e)),role:"tabpanel",id:i,"aria-labelledby":c}),o||a?n:null)},r}(s.Component);Ap.defaultProps={className:"react-tabs__tab-panel",forceRender:!1,selectedClassName:"react-tabs__tab-panel--selected"},Ap.propTypes={},Ap.tabsRole="TabPanel";var Ip,Cp,Tp,Rp,Pp=ka(mp)(_p||(_p=Ao(["\n > ul {\n list-style: none;\n padding: 0;\n margin: 0;\n margin: 0 -5px;\n\n > li {\n padding: 5px 10px;\n display: inline-block;\n\n background-color: ",";\n border-bottom: 1px solid rgba(0, 0, 0, 0.5);\n cursor: pointer;\n text-align: center;\n outline: none;\n color: ",";\n margin: 0\n ",";\n border: 1px solid ",";\n border-radius: 5px;\n min-width: 60px;\n font-size: 0.9em;\n font-weight: bold;\n\n &.react-tabs__tab--selected {\n color: ",";\n background: ",";\n &:focus {\n outline: auto;\n }\n }\n\n &:only-child {\n flex: none;\n min-width: 100px;\n }\n\n &.tab-success {\n color: ",";\n }\n\n &.tab-redirect {\n color: ",";\n }\n\n &.tab-info {\n color: ",";\n }\n\n &.tab-error {\n color: ",";\n }\n }\n }\n > .react-tabs__tab-panel {\n background: ",";\n & > div,\n & > pre {\n padding: ","px;\n margin: 0;\n }\n\n & > div > pre {\n padding: 0;\n }\n }\n"])),(function(e){return e.theme.codeBlock.backgroundColor}),(function(e){var t=e.theme;return zr(t.colors.tonalOffset,t.rightPanel.textColor)}),(function(e){var t=e.theme;return"".concat(t.spacing.unit,"px ").concat(t.spacing.unit,"px ").concat(t.spacing.unit,"px")}),(function(e){var t=e.theme;return zr(.05,t.codeBlock.backgroundColor)}),(function(e){return e.theme.colors.text.primary}),(function(e){return e.theme.rightPanel.textColor}),(function(e){return e.theme.colors.responses.success.tabTextColor}),(function(e){return e.theme.colors.responses.redirect.tabTextColor}),(function(e){return e.theme.colors.responses.info.tabTextColor}),(function(e){return e.theme.colors.responses.error.tabTextColor}),(function(e){return e.theme.codeBlock.backgroundColor}),(function(e){return 4*e.theme.spacing.unit})),jp=(ka(Pp)(Op||(Op=Ao(["\n > ul {\n display: block;\n > li {\n padding: 2px 5px;\n min-width: auto;\n margin: 0 15px 0 0;\n font-size: 13px;\n font-weight: normal;\n border-bottom: 1px dashed;\n color: ",";\n border-radius: 0;\n background: none;\n\n &:last-child {\n margin-right: 0;\n }\n\n &.react-tabs__tab--selected {\n color: ",";\n background: none;\n }\n }\n }\n > .react-tabs__tab-panel {\n & > div,\n & > pre {\n padding: ","px 0;\n }\n }\n"])),(function(e){var t=e.theme;return zr(t.colors.tonalOffset,t.rightPanel.textColor)}),(function(e){return e.theme.rightPanel.textColor}),(function(e){return 2*e.theme.spacing.unit})),ka.div(Ip||(Ip=Ao(["\n /**\n * Based on prism-dark.css\n */\n\n code[class*='language-'],\n pre[class*='language-'] {\n /* color: white;\n background: none; */\n text-shadow: 0 -0.1em 0.2em black;\n text-align: left;\n white-space: pre;\n word-spacing: normal;\n word-break: normal;\n word-wrap: normal;\n line-height: 1.5;\n\n -moz-tab-size: 4;\n -o-tab-size: 4;\n tab-size: 4;\n\n -webkit-hyphens: none;\n -moz-hyphens: none;\n -ms-hyphens: none;\n hyphens: none;\n }\n\n @media print {\n code[class*='language-'],\n pre[class*='language-'] {\n text-shadow: none;\n }\n }\n\n /* Code blocks */\n pre[class*='language-'] {\n padding: 1em;\n margin: 0.5em 0;\n overflow: auto;\n }\n\n .token.comment,\n .token.prolog,\n .token.doctype,\n .token.cdata {\n color: hsl(30, 20%, 50%);\n }\n\n .token.punctuation {\n opacity: 0.7;\n }\n\n .namespace {\n opacity: 0.7;\n }\n\n .token.property,\n .token.tag,\n .token.number,\n .token.constant,\n .token.symbol {\n color: #4a8bb3;\n }\n\n .token.boolean {\n color: #e64441;\n }\n\n .token.selector,\n .token.attr-name,\n .token.string,\n .token.char,\n .token.builtin,\n .token.inserted {\n color: #a0fbaa;\n & + a,\n & + a:visited {\n color: #4ed2ba;\n text-decoration: underline;\n }\n }\n\n .token.property.string {\n color: white;\n }\n\n .token.operator,\n .token.entity,\n .token.url,\n .token.variable {\n color: hsl(40, 90%, 60%);\n }\n\n .token.atrule,\n .token.attr-value,\n .token.keyword {\n color: hsl(350, 40%, 70%);\n }\n\n .token.regex,\n .token.important {\n color: #e90;\n }\n\n .token.important,\n .token.bold {\n font-weight: bold;\n }\n .token.italic {\n font-style: italic;\n }\n\n .token.entity {\n cursor: help;\n }\n\n .token.deleted {\n color: red;\n }\n\n ",";\n"])),Ea("Prism"))),Lp=ka.div(Cp||(Cp=Ao(["\n opacity: 0.7;\n transition: opacity 0.3s ease;\n text-align: right;\n &:focus-within {\n opacity: 1;\n }\n > button {\n background-color: transparent;\n border: 0;\n color: inherit;\n padding: 2px 10px;\n font-family: ",";\n font-size: ",";\n line-height: ",";\n cursor: pointer;\n outline: 0;\n\n :hover,\n :focus {\n background: rgba(255, 255, 255, 0.1);\n }\n }\n"])),(function(e){return e.theme.typography.fontFamily}),(function(e){return e.theme.typography.fontSize}),(function(e){return e.theme.typography.lineHeight})),Np=ka.div(Tp||(Tp=Ao(["\n &:hover "," {\n opacity: 1;\n }\n"])),Lp),Mp=ka(jp.withComponent("pre"))(Rp||(Rp=Ao(["\n font-family: ",";\n font-size: ",";\n overflow-x: auto;\n margin: 0;\n\n white-space: ",";\n"])),(function(e){return e.theme.typography.code.fontFamily}),(function(e){return e.theme.typography.code.fontSize}),(function(e){return e.theme.typography.code.wrap?"pre-wrap":"pre"}));function Dp(e){return getComputedStyle(e)}function Fp(e,t){for(var n in t){var r=t[n];"number"==typeof r&&(r+="px"),e.style[n]=r}return e}function zp(e){var t=document.createElement("div");return t.className=e,t}var Up="undefined"!=typeof Element&&(Element.prototype.matches||Element.prototype.webkitMatchesSelector||Element.prototype.mozMatchesSelector||Element.prototype.msMatchesSelector);function Bp(e,t){if(!Up)throw new Error("No element matching method supported");return Up.call(e,t)}function $p(e){e.remove?e.remove():e.parentNode&&e.parentNode.removeChild(e)}function qp(e,t){return Array.prototype.filter.call(e.children,(function(e){return Bp(e,t)}))}var Vp=function(e){return"ps__thumb-"+e},Wp=function(e){return"ps__rail-"+e},Hp="ps__child--consume",Yp="ps--focus",Gp="ps--clicking",Qp=function(e){return"ps--active-"+e},Xp=function(e){return"ps--scrolling-"+e},Kp={x:null,y:null};function Zp(e,t){var n=e.element.classList,r=Xp(t);n.contains(r)?clearTimeout(Kp[t]):n.add(r)}function Jp(e,t){Kp[t]=setTimeout((function(){return e.isAlive&&e.element.classList.remove(Xp(t))}),e.settings.scrollingThreshold)}var ef=function(e){this.element=e,this.handlers={}},tf={isEmpty:{configurable:!0}};ef.prototype.bind=function(e,t){void 0===this.handlers[e]&&(this.handlers[e]=[]),this.handlers[e].push(t),this.element.addEventListener(e,t,!1)},ef.prototype.unbind=function(e,t){var n=this;this.handlers[e]=this.handlers[e].filter((function(r){return!(!t||r===t)||(n.element.removeEventListener(e,r,!1),!1)}))},ef.prototype.unbindAll=function(){for(var e in this.handlers)this.unbind(e)},tf.isEmpty.get=function(){var e=this;return Object.keys(this.handlers).every((function(t){return 0===e.handlers[t].length}))},Object.defineProperties(ef.prototype,tf);var nf=function(){this.eventElements=[]};function rf(e){if("function"==typeof window.CustomEvent)return new CustomEvent(e);var t=document.createEvent("CustomEvent");return t.initCustomEvent(e,!1,!1,void 0),t}function of(e,t,n,r,o){var i;if(void 0===r&&(r=!0),void 0===o&&(o=!1),"top"===t)i=["contentHeight","containerHeight","scrollTop","y","up","down"];else{if("left"!==t)throw new Error("A proper axis should be provided");i=["contentWidth","containerWidth","scrollLeft","x","left","right"]}!function(e,t,n,r,o){var i=n[0],a=n[1],s=n[2],l=n[3],c=n[4],u=n[5];void 0===r&&(r=!0),void 0===o&&(o=!1);var p=e.element;e.reach[l]=null,p[s]<1&&(e.reach[l]="start"),p[s]>e[i]-e[a]-1&&(e.reach[l]="end"),t&&(p.dispatchEvent(rf("ps-scroll-"+l)),t<0?p.dispatchEvent(rf("ps-scroll-"+c)):t>0&&p.dispatchEvent(rf("ps-scroll-"+u)),r&&function(e,t){Zp(e,t),Jp(e,t)}(e,l)),e.reach[l]&&(t||o)&&p.dispatchEvent(rf("ps-"+l+"-reach-"+e.reach[l]))}(e,n,i,r,o)}function af(e){return parseInt(e,10)||0}nf.prototype.eventElement=function(e){var t=this.eventElements.filter((function(t){return t.element===e}))[0];return t||(t=new ef(e),this.eventElements.push(t)),t},nf.prototype.bind=function(e,t,n){this.eventElement(e).bind(t,n)},nf.prototype.unbind=function(e,t,n){var r=this.eventElement(e);r.unbind(t,n),r.isEmpty&&this.eventElements.splice(this.eventElements.indexOf(r),1)},nf.prototype.unbindAll=function(){this.eventElements.forEach((function(e){return e.unbindAll()})),this.eventElements=[]},nf.prototype.once=function(e,t,n){var r=this.eventElement(e),o=function(e){r.unbind(t,o),n(e)};r.bind(t,o)};var sf={isWebKit:"undefined"!=typeof document&&"WebkitAppearance"in document.documentElement.style,supportsTouch:"undefined"!=typeof window&&("ontouchstart"in window||"maxTouchPoints"in window.navigator&&window.navigator.maxTouchPoints>0||window.DocumentTouch&&document instanceof window.DocumentTouch),supportsIePointer:"undefined"!=typeof navigator&&navigator.msMaxTouchPoints,isChrome:"undefined"!=typeof navigator&&/Chrome/i.test(navigator&&navigator.userAgent)};function lf(e){var t=e.element,n=Math.floor(t.scrollTop),r=t.getBoundingClientRect();e.containerWidth=Math.ceil(r.width),e.containerHeight=Math.ceil(r.height),e.contentWidth=t.scrollWidth,e.contentHeight=t.scrollHeight,t.contains(e.scrollbarXRail)||(qp(t,Wp("x")).forEach((function(e){return $p(e)})),t.appendChild(e.scrollbarXRail)),t.contains(e.scrollbarYRail)||(qp(t,Wp("y")).forEach((function(e){return $p(e)})),t.appendChild(e.scrollbarYRail)),!e.settings.suppressScrollX&&e.containerWidth+e.settings.scrollXMarginOffset=e.railXWidth-e.scrollbarXWidth&&(e.scrollbarXLeft=e.railXWidth-e.scrollbarXWidth),e.scrollbarYTop>=e.railYHeight-e.scrollbarYHeight&&(e.scrollbarYTop=e.railYHeight-e.scrollbarYHeight),function(e,t){var n={width:t.railXWidth},r=Math.floor(e.scrollTop);t.isRtl?n.left=t.negativeScrollAdjustment+e.scrollLeft+t.containerWidth-t.contentWidth:n.left=e.scrollLeft,t.isScrollbarXUsingBottom?n.bottom=t.scrollbarXBottom-r:n.top=t.scrollbarXTop+r,Fp(t.scrollbarXRail,n);var o={top:r,height:t.railYHeight};t.isScrollbarYUsingRight?t.isRtl?o.right=t.contentWidth-(t.negativeScrollAdjustment+e.scrollLeft)-t.scrollbarYRight-t.scrollbarYOuterWidth-9:o.right=t.scrollbarYRight-e.scrollLeft:t.isRtl?o.left=t.negativeScrollAdjustment+e.scrollLeft+2*t.containerWidth-t.contentWidth-t.scrollbarYLeft-t.scrollbarYOuterWidth:o.left=t.scrollbarYLeft+e.scrollLeft,Fp(t.scrollbarYRail,o),Fp(t.scrollbarX,{left:t.scrollbarXLeft,width:t.scrollbarXWidth-t.railBorderXWidth}),Fp(t.scrollbarY,{top:t.scrollbarYTop,height:t.scrollbarYHeight-t.railBorderYWidth})}(t,e),e.scrollbarXActive?t.classList.add(Qp("x")):(t.classList.remove(Qp("x")),e.scrollbarXWidth=0,e.scrollbarXLeft=0,t.scrollLeft=!0===e.isRtl?e.contentWidth:0),e.scrollbarYActive?t.classList.add(Qp("y")):(t.classList.remove(Qp("y")),e.scrollbarYHeight=0,e.scrollbarYTop=0,t.scrollTop=0)}function cf(e,t){return e.settings.minScrollbarLength&&(t=Math.max(t,e.settings.minScrollbarLength)),e.settings.maxScrollbarLength&&(t=Math.min(t,e.settings.maxScrollbarLength)),t}function uf(e,t){var n=t[0],r=t[1],o=t[2],i=t[3],a=t[4],s=t[5],l=t[6],c=t[7],u=t[8],p=e.element,f=null,d=null,h=null;function m(t){t.touches&&t.touches[0]&&(t[o]=t.touches[0].pageY),p[l]=f+h*(t[o]-d),Zp(e,c),lf(e),t.stopPropagation(),t.preventDefault()}function v(){Jp(e,c),e[u].classList.remove(Gp),e.event.unbind(e.ownerDocument,"mousemove",m)}function g(t,a){f=p[l],a&&t.touches&&(t[o]=t.touches[0].pageY),d=t[o],h=(e[r]-e[n])/(e[i]-e[s]),a?e.event.bind(e.ownerDocument,"touchmove",m):(e.event.bind(e.ownerDocument,"mousemove",m),e.event.once(e.ownerDocument,"mouseup",v),t.preventDefault()),e[u].classList.add(Gp),t.stopPropagation()}e.event.bind(e[a],"mousedown",(function(e){g(e)})),e.event.bind(e[a],"touchstart",(function(e){g(e,!0)}))}var pf={"click-rail":function(e){e.element,e.event.bind(e.scrollbarY,"mousedown",(function(e){return e.stopPropagation()})),e.event.bind(e.scrollbarYRail,"mousedown",(function(t){var n=t.pageY-window.pageYOffset-e.scrollbarYRail.getBoundingClientRect().top>e.scrollbarYTop?1:-1;e.element.scrollTop+=n*e.containerHeight,lf(e),t.stopPropagation()})),e.event.bind(e.scrollbarX,"mousedown",(function(e){return e.stopPropagation()})),e.event.bind(e.scrollbarXRail,"mousedown",(function(t){var n=t.pageX-window.pageXOffset-e.scrollbarXRail.getBoundingClientRect().left>e.scrollbarXLeft?1:-1;e.element.scrollLeft+=n*e.containerWidth,lf(e),t.stopPropagation()}))},"drag-thumb":function(e){uf(e,["containerWidth","contentWidth","pageX","railXWidth","scrollbarX","scrollbarXWidth","scrollLeft","x","scrollbarXRail"]),uf(e,["containerHeight","contentHeight","pageY","railYHeight","scrollbarY","scrollbarYHeight","scrollTop","y","scrollbarYRail"])},keyboard:function(e){var t=e.element;e.event.bind(e.ownerDocument,"keydown",(function(n){if(!(n.isDefaultPrevented&&n.isDefaultPrevented()||n.defaultPrevented)&&(Bp(t,":hover")||Bp(e.scrollbarX,":focus")||Bp(e.scrollbarY,":focus"))){var r,o=document.activeElement?document.activeElement:e.ownerDocument.activeElement;if(o){if("IFRAME"===o.tagName)o=o.contentDocument.activeElement;else for(;o.shadowRoot;)o=o.shadowRoot.activeElement;if(Bp(r=o,"input,[contenteditable]")||Bp(r,"select,[contenteditable]")||Bp(r,"textarea,[contenteditable]")||Bp(r,"button,[contenteditable]"))return}var i=0,a=0;switch(n.which){case 37:i=n.metaKey?-e.contentWidth:n.altKey?-e.containerWidth:-30;break;case 38:a=n.metaKey?e.contentHeight:n.altKey?e.containerHeight:30;break;case 39:i=n.metaKey?e.contentWidth:n.altKey?e.containerWidth:30;break;case 40:a=n.metaKey?-e.contentHeight:n.altKey?-e.containerHeight:-30;break;case 32:a=n.shiftKey?e.containerHeight:-e.containerHeight;break;case 33:a=e.containerHeight;break;case 34:a=-e.containerHeight;break;case 36:a=e.contentHeight;break;case 35:a=-e.contentHeight;break;default:return}e.settings.suppressScrollX&&0!==i||e.settings.suppressScrollY&&0!==a||(t.scrollTop-=a,t.scrollLeft+=i,lf(e),function(n,r){var o=Math.floor(t.scrollTop);if(0===n){if(!e.scrollbarYActive)return!1;if(0===o&&r>0||o>=e.contentHeight-e.containerHeight&&r<0)return!e.settings.wheelPropagation}var i=t.scrollLeft;if(0===r){if(!e.scrollbarXActive)return!1;if(0===i&&n<0||i>=e.contentWidth-e.containerWidth&&n>0)return!e.settings.wheelPropagation}return!0}(i,a)&&n.preventDefault())}}))},wheel:function(e){var t=e.element;function n(n){var r=function(e){var t=e.deltaX,n=-1*e.deltaY;return void 0!==t&&void 0!==n||(t=-1*e.wheelDeltaX/6,n=e.wheelDeltaY/6),e.deltaMode&&1===e.deltaMode&&(t*=10,n*=10),t!=t&&n!=n&&(t=0,n=e.wheelDelta),e.shiftKey?[-n,-t]:[t,n]}(n),o=r[0],i=r[1];if(!function(e,n,r){if(!sf.isWebKit&&t.querySelector("select:focus"))return!0;if(!t.contains(e))return!1;for(var o=e;o&&o!==t;){if(o.classList.contains(Hp))return!0;var i=Dp(o);if(r&&i.overflowY.match(/(scroll|auto)/)){var a=o.scrollHeight-o.clientHeight;if(a>0&&(o.scrollTop>0&&r<0||o.scrollTop0))return!0}if(n&&i.overflowX.match(/(scroll|auto)/)){var s=o.scrollWidth-o.clientWidth;if(s>0&&(o.scrollLeft>0&&n<0||o.scrollLeft0))return!0}o=o.parentNode}return!1}(n.target,o,i)){var a=!1;e.settings.useBothWheelAxes?e.scrollbarYActive&&!e.scrollbarXActive?(i?t.scrollTop-=i*e.settings.wheelSpeed:t.scrollTop+=o*e.settings.wheelSpeed,a=!0):e.scrollbarXActive&&!e.scrollbarYActive&&(o?t.scrollLeft+=o*e.settings.wheelSpeed:t.scrollLeft-=i*e.settings.wheelSpeed,a=!0):(t.scrollTop-=i*e.settings.wheelSpeed,t.scrollLeft+=o*e.settings.wheelSpeed),lf(e),(a=a||function(n,r){var o=Math.floor(t.scrollTop),i=0===t.scrollTop,a=o+t.offsetHeight===t.scrollHeight,s=0===t.scrollLeft,l=t.scrollLeft+t.offsetWidth===t.scrollWidth;return!(Math.abs(r)>Math.abs(n)?i||a:s||l)||!e.settings.wheelPropagation}(o,i))&&!n.ctrlKey&&(n.stopPropagation(),n.preventDefault())}}void 0!==window.onwheel?e.event.bind(t,"wheel",n):void 0!==window.onmousewheel&&e.event.bind(t,"mousewheel",n)},touch:function(e){if(sf.supportsTouch||sf.supportsIePointer){var t=e.element,n={},r=0,o={},i=null;sf.supportsTouch?(e.event.bind(t,"touchstart",c),e.event.bind(t,"touchmove",u),e.event.bind(t,"touchend",p)):sf.supportsIePointer&&(window.PointerEvent?(e.event.bind(t,"pointerdown",c),e.event.bind(t,"pointermove",u),e.event.bind(t,"pointerup",p)):window.MSPointerEvent&&(e.event.bind(t,"MSPointerDown",c),e.event.bind(t,"MSPointerMove",u),e.event.bind(t,"MSPointerUp",p)))}function a(n,r){t.scrollTop-=r,t.scrollLeft-=n,lf(e)}function s(e){return e.targetTouches?e.targetTouches[0]:e}function l(e){return!(e.pointerType&&"pen"===e.pointerType&&0===e.buttons||(!e.targetTouches||1!==e.targetTouches.length)&&(!e.pointerType||"mouse"===e.pointerType||e.pointerType===e.MSPOINTER_TYPE_MOUSE))}function c(e){if(l(e)){var t=s(e);n.pageX=t.pageX,n.pageY=t.pageY,r=(new Date).getTime(),null!==i&&clearInterval(i)}}function u(i){if(l(i)){var c=s(i),u={pageX:c.pageX,pageY:c.pageY},p=u.pageX-n.pageX,f=u.pageY-n.pageY;if(function(e,n,r){if(!t.contains(e))return!1;for(var o=e;o&&o!==t;){if(o.classList.contains(Hp))return!0;var i=Dp(o);if(r&&i.overflowY.match(/(scroll|auto)/)){var a=o.scrollHeight-o.clientHeight;if(a>0&&(o.scrollTop>0&&r<0||o.scrollTop0))return!0}if(n&&i.overflowX.match(/(scroll|auto)/)){var s=o.scrollWidth-o.clientWidth;if(s>0&&(o.scrollLeft>0&&n<0||o.scrollLeft0))return!0}o=o.parentNode}return!1}(i.target,p,f))return;a(p,f),n=u;var d=(new Date).getTime(),h=d-r;h>0&&(o.x=p/h,o.y=f/h,r=d),function(n,r){var o=Math.floor(t.scrollTop),i=t.scrollLeft,a=Math.abs(n),s=Math.abs(r);if(s>a){if(r<0&&o===e.contentHeight-e.containerHeight||r>0&&0===o)return 0===window.scrollY&&r>0&&sf.isChrome}else if(a>s&&(n<0&&i===e.contentWidth-e.containerWidth||n>0&&0===i))return!0;return!0}(p,f)&&i.preventDefault()}}function p(){e.settings.swipeEasing&&(clearInterval(i),i=setInterval((function(){e.isInitialized?clearInterval(i):o.x||o.y?Math.abs(o.x)<.01&&Math.abs(o.y)<.01?clearInterval(i):(a(30*o.x,30*o.y),o.x*=.8,o.y*=.8):clearInterval(i)}),10))}}},ff=function(e,t){var n=this;if(void 0===t&&(t={}),"string"==typeof e&&(e=document.querySelector(e)),!e||!e.nodeName)throw new Error("no element is specified to initialize PerfectScrollbar");for(var r in this.element=e,e.classList.add("ps"),this.settings={handlers:["click-rail","drag-thumb","keyboard","wheel","touch"],maxScrollbarLength:null,minScrollbarLength:null,scrollingThreshold:1e3,scrollXMarginOffset:0,scrollYMarginOffset:0,suppressScrollX:!1,suppressScrollY:!1,swipeEasing:!0,useBothWheelAxes:!1,wheelPropagation:!0,wheelSpeed:1},t)this.settings[r]=t[r];this.containerWidth=null,this.containerHeight=null,this.contentWidth=null,this.contentHeight=null;var o,i,a=function(){return e.classList.add(Yp)},s=function(){return e.classList.remove(Yp)};this.isRtl="rtl"===Dp(e).direction,!0===this.isRtl&&e.classList.add("ps__rtl"),this.isNegativeScroll=(i=e.scrollLeft,e.scrollLeft=-1,o=e.scrollLeft<0,e.scrollLeft=i,o),this.negativeScrollAdjustment=this.isNegativeScroll?e.scrollWidth-e.clientWidth:0,this.event=new nf,this.ownerDocument=e.ownerDocument||document,this.scrollbarXRail=zp(Wp("x")),e.appendChild(this.scrollbarXRail),this.scrollbarX=zp(Vp("x")),this.scrollbarXRail.appendChild(this.scrollbarX),this.scrollbarX.setAttribute("tabindex",0),this.event.bind(this.scrollbarX,"focus",a),this.event.bind(this.scrollbarX,"blur",s),this.scrollbarXActive=null,this.scrollbarXWidth=null,this.scrollbarXLeft=null;var l=Dp(this.scrollbarXRail);this.scrollbarXBottom=parseInt(l.bottom,10),isNaN(this.scrollbarXBottom)?(this.isScrollbarXUsingBottom=!1,this.scrollbarXTop=af(l.top)):this.isScrollbarXUsingBottom=!0,this.railBorderXWidth=af(l.borderLeftWidth)+af(l.borderRightWidth),Fp(this.scrollbarXRail,{display:"block"}),this.railXMarginWidth=af(l.marginLeft)+af(l.marginRight),Fp(this.scrollbarXRail,{display:""}),this.railXWidth=null,this.railXRatio=null,this.scrollbarYRail=zp(Wp("y")),e.appendChild(this.scrollbarYRail),this.scrollbarY=zp(Vp("y")),this.scrollbarYRail.appendChild(this.scrollbarY),this.scrollbarY.setAttribute("tabindex",0),this.event.bind(this.scrollbarY,"focus",a),this.event.bind(this.scrollbarY,"blur",s),this.scrollbarYActive=null,this.scrollbarYHeight=null,this.scrollbarYTop=null;var c=Dp(this.scrollbarYRail);this.scrollbarYRight=parseInt(c.right,10),isNaN(this.scrollbarYRight)?(this.isScrollbarYUsingRight=!1,this.scrollbarYLeft=af(c.left)):this.isScrollbarYUsingRight=!0,this.scrollbarYOuterWidth=this.isRtl?function(e){var t=Dp(e);return af(t.width)+af(t.paddingLeft)+af(t.paddingRight)+af(t.borderLeftWidth)+af(t.borderRightWidth)}(this.scrollbarY):null,this.railBorderYWidth=af(c.borderTopWidth)+af(c.borderBottomWidth),Fp(this.scrollbarYRail,{display:"block"}),this.railYMarginHeight=af(c.marginTop)+af(c.marginBottom),Fp(this.scrollbarYRail,{display:""}),this.railYHeight=null,this.railYRatio=null,this.reach={x:e.scrollLeft<=0?"start":e.scrollLeft>=this.contentWidth-this.containerWidth?"end":null,y:e.scrollTop<=0?"start":e.scrollTop>=this.contentHeight-this.containerHeight?"end":null},this.isAlive=!0,this.settings.handlers.forEach((function(e){return pf[e](n)})),this.lastScrollTop=Math.floor(e.scrollTop),this.lastScrollLeft=e.scrollLeft,this.event.bind(this.element,"scroll",(function(e){return n.onScroll(e)})),lf(this)};ff.prototype.update=function(){this.isAlive&&(this.negativeScrollAdjustment=this.isNegativeScroll?this.element.scrollWidth-this.element.clientWidth:0,Fp(this.scrollbarXRail,{display:"block"}),Fp(this.scrollbarYRail,{display:"block"}),this.railXMarginWidth=af(Dp(this.scrollbarXRail).marginLeft)+af(Dp(this.scrollbarXRail).marginRight),this.railYMarginHeight=af(Dp(this.scrollbarYRail).marginTop)+af(Dp(this.scrollbarYRail).marginBottom),Fp(this.scrollbarXRail,{display:"none"}),Fp(this.scrollbarYRail,{display:"none"}),lf(this),of(this,"top",0,!1,!0),of(this,"left",0,!1,!0),Fp(this.scrollbarXRail,{display:""}),Fp(this.scrollbarYRail,{display:""}))},ff.prototype.onScroll=function(e){this.isAlive&&(lf(this),of(this,"top",this.element.scrollTop-this.lastScrollTop),of(this,"left",this.element.scrollLeft-this.lastScrollLeft),this.lastScrollTop=Math.floor(this.element.scrollTop),this.lastScrollLeft=this.element.scrollLeft)},ff.prototype.destroy=function(){this.isAlive&&(this.event.unbindAll(),$p(this.scrollbarX),$p(this.scrollbarY),$p(this.scrollbarXRail),$p(this.scrollbarYRail),this.removePsClasses(),this.element=null,this.scrollbarX=null,this.scrollbarY=null,this.scrollbarXRail=null,this.scrollbarYRail=null,this.isAlive=!1)},ff.prototype.removePsClasses=function(){this.element.className=this.element.className.split(" ").filter((function(e){return!e.match(/^ps([-_].+|)$/)})).join(" ")};var df,hf,mf=ff,vf=r(4875),gf=mf||t,yf=ya(df||(df=Ao(["",""])),vf.Z&&vf.Z.toString()),bf=ka.div(hf||(hf=Ao(["\n position: relative;\n"]))),xf=function(e){Eo(r,e);var t=Oo(r);function r(){var e;nr(this,r);for(var o=arguments.length,i=new Array(o),a=0;a2&&void 0!==arguments[2]?arguments[2]:new ko({});nr(this,e),this.options=i,n(this,"specUrl",void 0),n(this,"spec",void 0),n(this,"_refCounter",new Nf),n(this,"allowMergeRefs",!1),n(this,"byRef",(function(e){var t;if(o.spec){"#"!==e.charAt(0)&&(e="#"+e),e=decodeURIComponent(e);try{t=Ja.get(o.spec,e)}catch(e){}return t||{}}})),this.validate(t),this.preprocess(t),this.spec=t,this.allowMergeRefs=t.openapi.startsWith("3.1");var a=Jr?window.location.href:"";"string"==typeof r&&(this.specUrl=(0,ao.resolve)(a,r))}return or(e,[{key:"validate",value:function(e){if(void 0===e.openapi)throw new Error("Document must be valid OpenAPI 3.0.0 definition")}},{key:"preprocess",value:function(e){if(!this.options.noAutoAuth&&e.info&&e.components&&e.components.securitySchemes){var t=e.info.description||"";if(!qs.containsComponent(t,ks)&&!qs.containsComponent(t,Es)){var n="\x3c!-- ReDoc-Inject: <".concat(ks,"> --\x3e");e.info.description=function(e,t,n){var r=new RegExp("(^|\\n)#\\s?".concat(t,"\\s*\\n"),"i"),o=new RegExp("((\\n|^)#\\s*".concat(t,"\\s*(\\n|$)(?:.|\\n)*?)(\\n#|$)"),"i");if(r.test(e))return e.replace(o,"$1\n\n".concat(n,"\n$4"));var i=""===e||e.endsWith("\n\n")?"":e.endsWith("\n")?"\n":"\n\n";return"".concat(e).concat(i,"# ").concat(t,"\n\n").concat(n)}(t,"Authentication",n)}}}},{key:"isRef",value:function(e){return!!e&&void 0!==e.$ref&&null!==e.$ref}},{key:"resetVisited",value:function(){this._refCounter=new Nf}},{key:"exitRef",value:function(e){this.isRef(e)&&this._refCounter.exit(e.$ref)}},{key:"deref",value:function(e){var t=arguments.length>1&&void 0!==arguments[1]&&arguments[1],n=arguments.length>2&&void 0!==arguments[2]&&arguments[2];if(this.isRef(e)){var r=vs(e.$ref);if(r&&this.options.ignoreNamedSchemas.has(r))return{type:"object",title:r};var o=this.byRef(e.$ref),i=this._refCounter.visited(e.$ref);if(this._refCounter.visit(e.$ref),i&&!t)return Object.assign({},o,{"x-circular-ref":!0});var a=o;return this.isRef(o)&&(a=this.deref(o,!1,n),this.exitRef(o)),this.allowMergeRefs?this.mergeRefs(e,o,n):a}return e}},{key:"mergeRefs",value:function(e,t,n){e.$ref;var r=function(e,t){if(null==e)return{};var n,r,o=function(e,t){if(null==e)return{};var n,r,o={},i=Object.keys(e);for(r=0;r=0||(o[n]=e[n]);return o}(e,t);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);for(r=0;r=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(o[n]=e[n])}return o}(e,Lf),o=Object.keys(r);return 0===o.length?t:n&&o.some((function(e){return"description"!==e&&"title"!==e&&"externalDocs"!==e}))?{allOf:[t,r]}:a(a({},t),r)}},{key:"shalowDeref",value:function(e){return this.isRef(e)?this.byRef(e.$ref):e}},{key:"mergeAllOf",value:function(e,t){var n=this,r=arguments.length>2&&void 0!==arguments[2]&&arguments[2],o=arguments.length>3&&void 0!==arguments[3]?arguments[3]:new Set;if(t&&o.add(t),void 0===(e=this.hoistOneOfs(e)).allOf)return e;var i=a(a({},e),{},{allOf:void 0,parentRefs:[],title:e.title||vs(t)});void 0!==i.properties&&"object"==typeof i.properties&&(i.properties=a({},i.properties)),void 0!==i.items&&"object"==typeof i.items&&(i.items=a({},i.items));var s,l=e.allOf.map((function(e){var t;if(!(e&&e.$ref&&o.has(e.$ref))){var a=n.deref(e,r,!0),s=e.$ref||void 0,l=n.mergeAllOf(a,s,r,o);return(t=i.parentRefs).push.apply(t,es(l.parentRefs||[])),{$ref:s,schema:l}}})).filter((function(e){return void 0!==e})),c=ro(l);try{for(c.s();!(s=c.n()).done;){var u=s.value,p=u.$ref,f=u.schema;if(i.type!==f.type&&void 0!==i.type&&void 0!==f.type&&console.warn('Incompatible types in allOf at "'.concat(t,'": "').concat(i.type,'" and "').concat(f.type,'"')),void 0!==f.type&&(i.type=f.type),void 0!==f.properties)for(var d in i.properties=i.properties||{},f.properties)if(i.properties[d]){var h=this.mergeAllOf({allOf:[i.properties[d],f.properties[d]]},t+"/properties/"+d);i.properties[d]=h,this.exitParents(h)}else i.properties[d]=f.properties[d];void 0!==f.items&&(i.items=i.items||{},i.items=this.mergeAllOf({allOf:[i.items,f.items]},t+"/items")),void 0!==f.required&&(i.required=(i.required||[]).concat(f.required)),i=a(a({},f),i),p&&(i.parentRefs.push(p),void 0===i.title&&ms(p))}}catch(e){c.e(e)}finally{c.f()}return i}},{key:"findDerived",value:function(e){var t={},n=this.spec.components&&this.spec.components.schemas||{};for(var r in n){var o=this.deref(n[r]);void 0!==o.allOf&&o.allOf.find((function(t){return void 0!==t.$ref&&e.indexOf(t.$ref)>-1}))&&(t["#/components/schemas/"+r]=[o["x-discriminator-value"]||r])}return t}},{key:"exitParents",value:function(e){var t,n=ro(e.parentRefs||[]);try{for(n.s();!(t=n.n()).done;){var r=t.value;this.exitRef({$ref:r})}}catch(e){n.e(e)}finally{n.f()}}},{key:"hoistOneOfs",value:function(e){var t=this;if(void 0===e.allOf)return e;for(var n=e.allOf,r=0;r0?o.push.apply(o,es(e.getTagGroupsItems(t,void 0,r["x-tagGroups"],i,n))):o.push.apply(o,es(e.getTagsItems(t,i,void 0,void 0,n))),o}},{key:"addMarkdownItems",value:function(e,t,n,r){var o=new qs(r).extractHeadings(e||"");return o.length&&t&&t.description&&(t.description=qs.getTextBeforeHading(t.description,o[0].name)),function e(t,n){var r=arguments.length>2&&void 0!==arguments[2]?arguments[2]:1;return n.map((function(n){var o=new Ff("section",n,t);return o.depth=r,n.items&&(o.items=e(o,n.items,r+1)),qs.containsComponent(o.description||"",ks)&&_s(o.id+"/"),o}))}(t,o,n)}},{key:"getTagGroupsItems",value:function(t,n,r,o,i){var a,s=[],l=ro(r);try{for(l.s();!(a=l.n()).done;){var c=a.value,u=new Ff("group",c,n);u.depth=0,u.items=e.getTagsItems(t,o,u,c,i),s.push(u)}}catch(e){l.e(e)}finally{l.f()}return s}},{key:"getTagsItems",value:function(t,n,r,o,i){var a,s=[],l=ro((void 0===o?Object.keys(n):o.tags).map((function(e){return n[e]?(n[e].used=!0,n[e]):(console.warn('Non-existing tag "'.concat(e,'" is added to the group "').concat(o.name,'"')),null)})));try{for(l.s();!(a=l.n()).done;){var c=a.value;if(c){var u=new Ff("tag",c,r);if(u.depth=1,""!==c.name)u.items=[].concat(es(e.addMarkdownItems(c.description||"",u,u.depth+1,i)),es(this.getOperationsItems(t,u,c,u.depth+1,i))),s.push(u);else{var p=[].concat(es(e.addMarkdownItems(c.description||"",u,u.depth+1,i)),es(this.getOperationsItems(t,void 0,c,u.depth+1,i)));s.push.apply(s,es(p))}}}}catch(e){l.e(e)}finally{l.f()}return s}},{key:"getOperationsItems",value:function(e,t,n,r,o){if(0===n.operations.length)return[];var i,a=[],s=ro(n.operations);try{for(s.s();!(i=s.n()).done;){var l=i.value,c=new Jl(e,l,t,o);c.depth=r,a.push(c)}}catch(e){s.e(e)}finally{s.f()}return a}},{key:"getTagsWithOperations",value:function(e,t){var r,o={},i=t["x-webhooks"]||t.webhooks,s=ro(t.tags||[]);try{for(s.s();!(r=s.n()).done;){var l=r.value;o[l.name]=a(a({},l),{},{operations:[]})}}catch(e){s.e(e)}finally{s.f()}function c(e,t,r){for(var i=0,s=Object.keys(t);i=s.flatItems.length-1&&e);){if(e){var r=s.getElementAtOrFirstChild(n+1);if(s.scroll.isElementBellow(r))break}else{var o=s.getElementAt(n);if(s.scroll.isElementAbove(o))break}n+=t}s.activate(s.flatItems[n],!0,!0)})),n(this,"updateOnHistory",(function(){var e,t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:s.history.currentId;t&&((e=s.flatItems.find((function(e){return e.id===t})))?s.activateAndScroll(e,!1):(t.startsWith(Ss)&&(e=s.flatItems.find((function(e){return Ss.startsWith(e.id)})),s.activate(e)),s.scroll.scrollIntoViewBySelector("[".concat(Uf,'="').concat(t,'"]'))))})),n(this,"getItemById",(function(e){return s.flatItems.find((function(t){return t.id===e}))})),an(this),this.items=t.contentItems,this.flatItems=(i=this.items||[],"items",a=[],function e(t){var n,r=ro(t);try{for(r.s();!(n=r.n()).done;){var o=n.value;a.push(o),o.items&&e(o.items)}}catch(e){r.e(e)}finally{r.f()}}(i),a),this.flatItems.forEach((function(e,t){return e.absoluteIdx=t})),this.subscribe()}return or(e,[{key:"subscribe",value:function(){this._unsubscribe=this.scroll.subscribe(this.updateOnScroll),this._hashUnsubscribe=this.history.subscribe(this.updateOnHistory)}},{key:"toggleSidebar",value:function(){this.sideBarOpened=!this.sideBarOpened}},{key:"closeSidebar",value:function(){this.sideBarOpened=!1}},{key:"getElementAt",value:function(e){var t=this.flatItems[e];return t&&eo("[".concat(Uf,'="').concat(t.id,'"]'))||null}},{key:"getElementAtOrFirstChild",value:function(e){var t=this.flatItems[e];return t&&"group"===t.type&&(t=t.items[0]),t&&eo("[".concat(Uf,'="').concat(t.id,'"]'))||null}},{key:"activeItem",get:function(){return this.flatItems[this.activeItemIdx]||void 0}},{key:"activate",value:function(e){var t=!(arguments.length>1&&void 0!==arguments[1])||arguments[1],n=arguments.length>2&&void 0!==arguments[2]&&arguments[2];(this.activeItem&&this.activeItem.id)!==(e&&e.id)&&(e&&"group"===e.type||(this.deactivate(this.activeItem),e?e.depth<=0||(this.activeItemIdx=e.absoluteIdx,t&&this.history.replace(e.id,n),e.activate(),e.expand()):this.history.replace("",n)))}},{key:"deactivate",value:function(e){if(void 0!==e)for(e.deactivate();void 0!==e;)e.collapse(),e=e.parent}},{key:"activateAndScroll",value:function(e,t,n){var r=e&&this.getItemById(e.id)||e;this.activate(r,t,n),this.scrollToActive(),r&&r.items.length||this.closeSidebar()}},{key:"scrollToActive",value:function(){this.scroll.scrollIntoView(this.getElementAt(this.activeItemIdx))}},{key:"dispose",value:function(){this._unsubscribe(),this._hashUnsubscribe()}}],[{key:"updateOnHistory",value:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:Ms.currentId,t=arguments.length>1?arguments[1]:void 0;e&&t.scrollIntoViewBySelector("[".concat(Uf,'="').concat(e,'"]'))}}]),e}()).prototype,"activeItemIdx",[Re],{configurable:!0,enumerable:!0,writable:!0,initializer:function(){return-1}}),Rf=Ya(Cf.prototype,"sideBarOpened",[Re],{configurable:!0,enumerable:!0,writable:!0,initializer:function(){return!1}}),Ya(Cf.prototype,"toggleSidebar",[Tt],Object.getOwnPropertyDescriptor(Cf.prototype,"toggleSidebar"),Cf.prototype),Ya(Cf.prototype,"closeSidebar",[Tt],Object.getOwnPropertyDescriptor(Cf.prototype,"closeSidebar"),Cf.prototype),Ya(Cf.prototype,"activate",[Tt],Object.getOwnPropertyDescriptor(Cf.prototype,"activate"),Cf.prototype),Ya(Cf.prototype,"activateAndScroll",[If],Object.getOwnPropertyDescriptor(Cf.prototype,"activateAndScroll"),Cf.prototype),Cf),$f="scroll",qf=(100,Pf=function(e,t,n){var r,o,i,a,s,l,c,u;n.value=(r=n.value,o=100,l=null,c=0,u=function(){c=(new Date).getTime(),l=null,s=r.apply(i,a),l||(i=a=null)},function(){var e=(new Date).getTime(),t=o-(e-c);return i=this,a=arguments,t<=0||t>o?(l&&(clearTimeout(l),l=null),c=e,s=r.apply(i,a),l||(i=a=null)):l||(l=setTimeout(u,t)),s})},Ya((jf=function(){function e(t){nr(this,e),this.options=t,n(this,"_scrollParent",void 0),n(this,"_emiter",void 0),n(this,"_prevOffsetY",0),this._scrollParent=Jr?window:void 0,this._emiter=new Qa,this.bind()}return or(e,[{key:"bind",value:function(){this._prevOffsetY=this.scrollY(),this._scrollParent&&this._scrollParent.addEventListener("scroll",this.handleScroll)}},{key:"dispose",value:function(){this._scrollParent&&this._scrollParent.removeEventListener("scroll",this.handleScroll),this._emiter.removeAllListeners($f)}},{key:"scrollY",value:function(){return"undefined"!=typeof HTMLElement&&this._scrollParent instanceof HTMLElement?this._scrollParent.scrollTop:void 0!==this._scrollParent?this._scrollParent.pageYOffset:0}},{key:"isElementBellow",value:function(e){if(null!==e)return e.getBoundingClientRect().top>this.options.scrollYOffset()}},{key:"isElementAbove",value:function(e){if(null!==e){var t=e.getBoundingClientRect().top;return(t>0?Math.floor(t):Math.ceil(t))<=this.options.scrollYOffset()}}},{key:"subscribe",value:function(e){var t=this._emiter.addListener($f,e);return function(){return t.removeListener($f,e)}}},{key:"scrollIntoView",value:function(e){null!==e&&(e.scrollIntoView(),this._scrollParent&&this._scrollParent.scrollBy&&this._scrollParent.scrollBy(0,1-this.options.scrollYOffset()))}},{key:"scrollIntoViewBySelector",value:function(e){var t=eo(e);this.scrollIntoView(t)}},{key:"handleScroll",value:function(){var e=this.scrollY()-this._prevOffsetY>0;this._prevOffsetY=this.scrollY(),this._emiter.emit($f,e)}}]),e}()).prototype,"handleScroll",[Ga.bind,Pf],Object.getOwnPropertyDescriptor(jf.prototype,"handleScroll"),jf.prototype),jf),Vf=function(){function e(){nr(this,e),n(this,"searchWorker",function(){var e;if(Jr)try{e=r(5114)}catch(t){e=r(308).default}else e=r(308).default;return new e}())}var t;return or(e,[{key:"indexItems",value:function(e){var t=this;!function e(n){n.forEach((function(n){"group"!==n.type&&t.add(n.name,n.description||"",n.id),e(n.items)}))}(e),this.searchWorker.done()}},{key:"add",value:function(e,t,n){this.searchWorker.add(e,t,n)}},{key:"dispose",value:function(){this.searchWorker.terminate(),this.searchWorker.dispose()}},{key:"search",value:function(e){return this.searchWorker.search(e)}},{key:"toJS",value:(t=Fa(Ua().mark((function e(){return Ua().wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return e.abrupt("return",this.searchWorker.toJS());case 1:case"end":return e.stop()}}),e,this)}))),function(){return t.apply(this,arguments)})},{key:"load",value:function(e){this.searchWorker.load(e)}},{key:"fromExternalJS",value:function(e,t){e&&t&&this.searchWorker.fromExternalJS(e,t)}}]),e}();function Wf(e){var t=e.Label,n=void 0===t?Gu:t,r=e.Dropdown,o=void 0===r?Yu:r;return 1===e.options.length?s.createElement(n,null,e.options[0].value):s.createElement(o,ir({},e,{searchable:!1}))}var Hf,Yf,Gf=r(7856),Qf=ga(Hf||(Hf=Ao(["\n a {\n text-decoration: none;\n color: ",";\n\n &:visited {\n color: ",";\n }\n\n &:hover {\n color: ",";\n }\n }\n"])),(function(e){return e.theme.typography.links.color}),(function(e){return e.theme.typography.links.visited}),(function(e){return e.theme.typography.links.hover})),Xf=ka(jp)(Yf||(Yf=Ao(["\n\n font-family: ",";\n font-weight: ",";\n line-height: ",";\n\n p {\n &:last-child {\n margin-bottom: 0;\n }\n }\n\n ","\n\n ","\n\n h1 {\n ",";\n color: ",";\n margin-top: 0;\n }\n\n h2 {\n ",";\n color: ",";\n }\n\n code {\n color: ",";\n background-color: ",";\n\n font-family: ",";\n border-radius: 2px;\n border: 1px solid rgba(38, 50, 56, 0.1);\n padding: 0 ","px;\n font-size: ",";\n font-weight: ",";\n\n word-break: break-word;\n }\n\n pre {\n font-family: ",";\n white-space:",";\n background-color: ",";\n color: white;\n padding: ","px;\n overflow-x: auto;\n line-height: normal;\n border-radius: 0px;\n border: 1px solid rgba(38, 50, 56, 0.1);\n\n code {\n background-color: transparent;\n color: white;\n padding: 0;\n\n &:before,\n &:after {\n content: none;\n }\n }\n }\n\n blockquote {\n margin: 0;\n margin-bottom: 1em;\n padding: 0 15px;\n color: #777;\n border-left: 4px solid #ddd;\n }\n\n img {\n max-width: 100%;\n box-sizing: content-box;\n }\n\n ul,\n ol {\n padding-left: 2em;\n margin: 0;\n margin-bottom: 1em;\n\n ul, ol {\n margin-bottom: 0;\n margin-top: 0;\n }\n }\n\n table {\n display: block;\n width: 100%;\n overflow: auto;\n word-break: normal;\n word-break: keep-all;\n border-collapse: collapse;\n border-spacing: 0;\n margin-top: 1.5em;\n margin-bottom: 1.5em;\n }\n\n table tr {\n background-color: #fff;\n border-top: 1px solid #ccc;\n\n &:nth-child(2n) {\n background-color: ",";\n }\n }\n\n table th,\n table td {\n padding: 6px 13px;\n border: 1px solid #ddd;\n }\n\n table th {\n text-align: left;\n font-weight: bold;\n }\n\n ",";\n\n ","\n\n ",";\n"])),(function(e){return e.theme.typography.fontFamily}),(function(e){return e.theme.typography.fontWeightRegular}),(function(e){return e.theme.typography.lineHeight}),(function(e){return e.compact&&"\n p:first-child {\n margin-top: 0;\n }\n p:last-child {\n margin-bottom: 0;\n }\n "}),(function(e){return e.inline&&" p {\n display: inline-block;\n }"}),ac(1),(function(e){return e.theme.colors.primary.main}),ac(2),(function(e){return e.theme.colors.text.primary}),(function(e){return e.theme.typography.code.color}),(function(e){return e.theme.typography.code.backgroundColor}),(function(e){return e.theme.typography.code.fontFamily}),(function(e){return e.theme.spacing.unit}),(function(e){return e.theme.typography.code.fontSize}),(function(e){return e.theme.typography.code.fontWeight}),(function(e){return e.theme.typography.code.fontFamily}),(function(e){return e.theme.typography.code.wrap?"pre-wrap":"pre"}),(function(e){return e.theme.codeBlock.backgroundColor}),(function(e){return 4*e.theme.spacing.unit}),(function(e){return e.theme.schema.nestedBackground}),yc(".share-link"),Qf,Ea("Markdown")),Kf=Xf.withComponent("span");function Zf(e){var t=e.inline?Kf:Xf;return s.createElement(Ma,null,(function(n){return s.createElement(t,ir({className:"redoc-markdown "+(e.className||""),dangerouslySetInnerHTML:{__html:(r=n.untrustedSpec,o=e.html,r?Gf.sanitize(o):o)},"data-role":e["data-role"]},e));var r,o}))}var Jf,ed,td,nd,rd,od,id=function(e){Eo(n,e);var t=Oo(n);function n(){return nr(this,n),t.apply(this,arguments)}return or(n,[{key:"render",value:function(){var e=this.props,t=e.source,n=e.inline,r=e.compact,o=e.className,i=e["data-role"],a=new qs;return s.createElement(Zf,{html:a.renderMd(t),inline:n,compact:r,className:o,"data-role":i})}}]),n}(s.Component),ad=ka.div(Jf||(Jf=Ao(["\n position: relative;\n"]))),sd=ka.div(ed||(ed=Ao(["\n position: absolute;\n min-width: 80px;\n max-width: 500px;\n background: #fff;\n bottom: 100%;\n left: 50%;\n margin-bottom: 10px;\n transform: translateX(-50%);\n\n border-radius: 4px;\n padding: 0.3em 0.6em;\n text-align: center;\n box-shadow: 0px 0px 5px 0px rgba(204, 204, 204, 1);\n"]))),ld=ka.div(td||(td=Ao(["\n background: #fff;\n color: #000;\n display: inline;\n font-size: 0.85em;\n white-space: nowrap;\n"]))),cd=ka.div(nd||(nd=Ao(["\n position: absolute;\n width: 0;\n height: 0;\n bottom: -5px;\n left: 50%;\n margin-left: -5px;\n border-left: solid transparent 5px;\n border-right: solid transparent 5px;\n border-top: solid #fff 5px;\n"]))),ud=ka.div(rd||(rd=Ao(["\n position: absolute;\n width: 100%;\n height: 20px;\n bottom: -20px;\n"]))),pd=function(e){Eo(n,e);var t=Oo(n);function n(){return nr(this,n),t.apply(this,arguments)}return or(n,[{key:"render",value:function(){var e=this.props,t=e.open,n=e.title,r=e.children;return s.createElement(ad,null,r,t&&s.createElement(sd,null,s.createElement(ld,null,n),s.createElement(cd,null),s.createElement(ud,null)))}}]),n}(s.Component),fd="undefined"!=typeof document&&document.queryCommandSupported&&document.queryCommandSupported("copy"),dd=function(){function e(){nr(this,e)}return or(e,null,[{key:"isSupported",value:function(){return fd}},{key:"selectElement",value:function(e){var t,n;document.body.createTextRange?((t=document.body.createTextRange()).moveToElementText(e),t.select()):document.createRange&&window.getSelection&&(n=window.getSelection(),(t=document.createRange()).selectNodeContents(e),n.removeAllRanges(),n.addRange(t))}},{key:"deselect",value:function(){if(document.selection)document.selection.empty();else if(window.getSelection){var e=window.getSelection();e&&e.removeAllRanges()}}},{key:"copySelected",value:function(){var e;try{e=document.execCommand("copy")}catch(t){e=!1}return e}},{key:"copyElement",value:function(t){e.selectElement(t);var n=e.copySelected();return n&&e.deselect(),n}},{key:"copyCustom",value:function(t){var n=document.createElement("textarea");n.style.position="fixed",n.style.top="0",n.style.left="0",n.style.width="2em",n.style.height="2em",n.style.padding="0",n.style.border="none",n.style.outline="none",n.style.boxShadow="none",n.style.background="transparent",n.value=t,document.body.appendChild(n),n.select();var r=e.copySelected();return document.body.removeChild(n),r}}]),e}(),hd=function(e){Eo(r,e);var t=Oo(r);function r(e){var o;return nr(this,r),n(ar(o=t.call(this,e)),"copy",(function(){var e="string"==typeof o.props.data?o.props.data:JSON.stringify(o.props.data,null,2);dd.copyCustom(e),o.showTooltip()})),n(ar(o),"renderCopyButton",(function(){return s.createElement("button",{onClick:o.copy},s.createElement(pd,{title:dd.isSupported()?"Copied":"Not supported in your browser",open:o.state.tooltipShown},"Copy"))})),o.state={tooltipShown:!1},o}return or(r,[{key:"render",value:function(){return this.props.children({renderCopyButton:this.renderCopyButton})}},{key:"showTooltip",value:function(){var e=this;this.setState({tooltipShown:!0}),setTimeout((function(){e.setState({tooltipShown:!1})}),1500)}}]),r}(s.PureComponent),md=1;function vd(e,t){md=1;var n="";return n+='',n+="",n+=wd(e,t),(n+="")+"
"}function gd(e){return void 0!==e?e.toString().replace(/&/g,"&").replace(/"/g,""").replace(//g,">"):""}function yd(e){return JSON.stringify(e).slice(1,-1)}function bd(e,t){return''+gd(e)+" "}function xd(e){return''+e+" "}function wd(e,t){var n=typeof e,r="";return null==e?r+=bd("null","token keyword"):e&&e.constructor===Array?(md++,r+=function(e,t){for(var n=md>t?"collapsed":"",r=' ').concat(xd("["),''),o=!1,i=e.length,a=0;a',r+=wd(e[a],t),a
";return r+="".concat(xd("]")),o||(r=xd("[ ]")),r}(e,t),md--):e&&e.constructor===Date?r+=bd('"'+e.toISOString()+'"',"token string"):"object"===n?(md++,r+=function(e,t){for(var n=md>t?"collapsed":"",r=Object.keys(e),o=r.length,i=' ').concat(xd("{"),''),a=!1,s=0;s',i+='
"'+gd(l)+'" : ',i+=wd(e[l],t),s
"}return i+="".concat(xd("}")),a||(i=xd("{ }")),i}(e,t),md--):"number"===n?r+=bd(e,"token number"):"string"===n?/^(http|https):\/\/[^\s]+$/.test(e)?r+=bd('"',"token string")+''+gd(yd(e))+" "+bd('"',"token string"):r+=bd('"'+yd(e)+'"',"token string"):"boolean"===n&&(r+=bd(e,"token boolean")),r}var kd,Ed,Sd,_d,Od,Ad,Id,Cd=ga(od||(od=Ao(["\n .redoc-json code > .collapser {\n display: none;\n pointer-events: none;\n }\n\n font-family: ",";\n font-size: ",";\n\n white-space: ",";\n contain: content;\n overflow-x: auto;\n\n .callback-function {\n color: gray;\n }\n\n .collapser:after {\n content: '-';\n cursor: pointer;\n }\n\n .collapsed > .collapser:after {\n content: '+';\n cursor: pointer;\n }\n\n .ellipsis:after {\n content: ' … ';\n }\n\n .collapsible {\n margin-left: 2em;\n }\n\n .hoverable {\n padding-top: 1px;\n padding-bottom: 1px;\n padding-left: 2px;\n padding-right: 2px;\n border-radius: 2px;\n }\n\n .hovered {\n background-color: rgba(235, 238, 249, 1);\n }\n\n .collapser {\n background-color: transparent;\n border: 0;\n color: #fff;\n font-family: ",";\n font-size: ",";\n padding-right: 6px;\n padding-left: 6px;\n padding-top: 0;\n padding-bottom: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n width: 15px;\n height: 15px;\n position: absolute;\n top: 4px;\n left: -1.5em;\n cursor: default;\n user-select: none;\n -webkit-user-select: none;\n padding: 2px;\n &:focus {\n outline-color: #fff;\n outline-style: dotted;\n outline-width: 1px;\n }\n }\n\n ul {\n list-style-type: none;\n padding: 0px;\n margin: 0px 0px 0px 26px;\n }\n\n li {\n position: relative;\n display: block;\n }\n\n .hoverable {\n display: inline-block;\n }\n\n .selected {\n outline-style: solid;\n outline-width: 1px;\n outline-style: dotted;\n }\n\n .collapsed > .collapsible {\n display: none;\n }\n\n .ellipsis {\n display: none;\n }\n\n .collapsed > .ellipsis {\n display: inherit;\n }\n"])),(function(e){return e.theme.typography.code.fontFamily}),(function(e){return e.theme.typography.code.fontSize}),(function(e){return e.theme.typography.code.wrap?"pre-wrap":"pre"}),(function(e){return e.theme.typography.code.fontFamily}),(function(e){return e.theme.typography.code.fontSize})),Td=ka.div(kd||(kd=Ao(["\n &:hover > "," {\n opacity: 1;\n }\n"])),Lp),Rd=ka(function(e){Eo(r,e);var t=Oo(r);function r(){var e;nr(this,r);for(var o=arguments.length,i=new Array(o),a=0;a1){var i=o.map((function(e,n){return{value:t[e].summary||e,idx:n}})),a=t[o[e]],l=a.description;return s.createElement(Vd,null,s.createElement(Ud,null,s.createElement(zd,null,"Example"),this.props.renderDropdown({value:i[e].value,options:i,onChange:this.switchMedia,ariaLabel:"Example"})),s.createElement("div",null,l&&s.createElement(id,{source:l}),s.createElement(Nd,{example:a,mimeType:n})))}var c=t[o[0]];return s.createElement(Vd,null,c.description&&s.createElement(id,{source:c.description}),s.createElement(Nd,{example:c,mimeType:n}))}}]),r}(s.Component),Vd=ka.div(Dd||(Dd=Ao(["\n margin-top: 15px;\n"])));if(!s.useState)throw new Error("mobx-react-lite requires React with Hooks support");if(!an)throw new Error("mobx-react-lite@3 requires mobx at least version 6 to be available");function Wd(e){e()}var Hd=[];function Yd(e){return zt(Yn(e,t));var t}var Gd="undefined"==typeof FinalizationRegistry?void 0:FinalizationRegistry;function Qd(e){return{reaction:e,mounted:!1,changedBeforeMount:!1,cleanAt:Date.now()+Xd}}var Xd=1e4,Kd=Gd?function(e){var t=new Map,n=1,r=new e((function(e){var n=t.get(e);n&&(n.reaction.dispose(),t.delete(e))}));return{addReactionToTrack:function(e,o,i){var a=n++;return r.register(i,a,e),e.current=Qd(o),e.current.finalizationRegistryCleanupToken=a,t.set(a,e.current),e.current},recordReactionAsCommitted:function(e){r.unregister(e),e.current&&e.current.finalizationRegistryCleanupToken&&t.delete(e.current.finalizationRegistryCleanupToken)},forceCleanupTimerToRunNowForTests:function(){},resetCleanupScheduleForTests:function(){}}}(Gd):function(){var e,t=new Set;function n(){void 0===e&&(e=setTimeout(r,1e4))}function r(){e=void 0;var r=Date.now();t.forEach((function(e){var n=e.current;n&&r>=n.cleanAt&&(n.reaction.dispose(),e.current=null,t.delete(e))})),t.size>0&&n()}return{addReactionToTrack:function(e,r,o){var i;return e.current=Qd(r),i=e,t.add(i),n(),e.current},recordReactionAsCommitted:function(e){t.delete(e)},forceCleanupTimerToRunNowForTests:function(){e&&(clearTimeout(e),r())},resetCleanupScheduleForTests:function(){var n,r;if(t.size>0){try{for(var o=function(e){var t="function"==typeof Symbol&&Symbol.iterator,n=t&&e[t],r=0;if(n)return n.call(e);if(e&&"number"==typeof e.length)return{next:function(){return e&&r>=e.length&&(e=void 0),{value:e&&e[r++],done:!e}}};throw new TypeError(t?"Object is not iterable.":"Symbol.iterator is not defined.")}(t),i=o.next();!i.done;i=o.next()){var a=i.value,s=a.current;s&&(s.reaction.dispose(),a.current=null)}}catch(e){n={error:e}}finally{try{i&&!i.done&&(r=o.return)&&r.call(o)}finally{if(n)throw n.error}}t.clear()}e&&(clearTimeout(e),e=void 0)}}}(),Zd=Kd.addReactionToTrack,Jd=Kd.recordReactionAsCommitted,eh=(Kd.resetCleanupScheduleForTests,Kd.forceCleanupTimerToRunNowForTests,!1);function th(){return eh}function nh(e){return"observer"+e}var rh=function(){};function oh(e,t){if(void 0===t&&(t="observed"),th())return e();var n,r=function(e,t){var n="function"==typeof Symbol&&e[Symbol.iterator];if(!n)return e;var r,o,i=n.call(e),a=[];try{for(;(void 0===t||t-- >0)&&!(r=i.next()).done;)a.push(r.value)}catch(e){o={error:e}}finally{try{r&&!r.done&&(n=i.return)&&n.call(i)}finally{if(o)throw o.error}}return a}(s.useState(new rh),1)[0],o=(n=function(e,t){var n="function"==typeof Symbol&&e[Symbol.iterator];if(!n)return e;var r,o,i=n.call(e),a=[];try{for(;(void 0===t||t-- >0)&&!(r=i.next()).done;)a.push(r.value)}catch(e){o={error:e}}finally{try{r&&!r.done&&(n=i.return)&&n.call(i)}finally{if(o)throw o.error}}return a}((0,s.useState)(0),2)[1],(0,s.useCallback)((function(){n((function(e){return e+1}))}),Hd)),i=s.useRef(null);if(!i.current)var a=new yt(nh(t),(function(){l.mounted?o():l.changedBeforeMount=!0})),l=Zd(i,a,r);var c,u,p=i.current.reaction;if(s.useDebugValue(p,Yd),s.useEffect((function(){return Jd(i),i.current?(i.current.mounted=!0,i.current.changedBeforeMount&&(i.current.changedBeforeMount=!1,o())):(i.current={reaction:new yt(nh(t),(function(){o()})),mounted:!0,changedBeforeMount:!1,cleanAt:1/0},o()),function(){i.current.reaction.dispose(),i.current=null}}),[]),p.track((function(){try{c=e()}catch(e){u=e}})),u)throw u;return c}var ih=function(){return(ih=Object.assign||function(e){for(var t,n=1,r=arguments.length;n2?r-2:0),i=2;i"}function Ah(e){var t=this;if(!0===th())return e.call(this);dh(this,Eh,!1),dh(this,Sh,!1);var n=Oh(this),r=e.bind(this),o=!1,i=new yt(n+".render()",(function(){if(!o&&(o=!0,!0!==t[kh])){var e=!0;try{dh(t,Sh,!0),t[Eh]||s.Component.prototype.forceUpdate.call(t),e=!1}finally{dh(t,Sh,!1),e&&i.dispose()}}}));function a(){o=!1;var e=void 0,t=void 0;if(i.track((function(){try{t=function(e,t){var n=qe(e);try{return t()}finally{Ve(n)}}(!1,r)}catch(t){e=t}})),e)throw e;return t}return i.reactComponent=this,a[xh]=i,this.render=a,a.call(this)}function Ih(e,t){return th()&&console.warn("[mobx-react] It seems that a re-rendering of a React component is triggered while in static (server-side) mode. Please make sure components are rendered only once server-side."),this.state!==t||!ph(this.props,e)}function Ch(e,t){var n=uh("reactProp_"+t+"_valueHolder"),r=uh("reactProp_"+t+"_atomHolder");function o(){return this[r]||dh(this,r,K("reactive "+t)),this[r]}Object.defineProperty(e,t,{configurable:!0,enumerable:!0,get:function(){var e=!1;return it&&at&&(e=it(!0)),o.call(this).reportObserved(),it&&at&&at(e),this[n]},set:function(e){this[Sh]||ph(this[n],e)?dh(this,n,e):(dh(this,n,e),dh(this,Eh,!0),o.call(this).reportChanged(),dh(this,Eh,!1))}})}var Th,Rh,Ph,jh,Lh,Nh,Mh,Dh,Fh,zh,Uh,Bh,$h="function"==typeof Symbol&&Symbol.for,qh=$h?Symbol.for("react.forward_ref"):"function"==typeof s.forwardRef&&(0,s.forwardRef)((function(e){return null})).$$typeof,Vh=$h?Symbol.for("react.memo"):"function"==typeof s.memo&&(0,s.memo)((function(e){return null})).$$typeof;function Wh(e){if(!0===e.isMobxInjector&&console.warn("Mobx observer: You are trying to use 'observer' on a component that already has 'inject'. Please apply 'observer' before applying 'inject'"),Vh&&e.$$typeof===Vh)throw new Error("Mobx observer: You are trying to use 'observer' on a function component wrapped in either another observer or 'React.memo'. The observer already applies 'React.memo' for you.");if(qh&&e.$$typeof===qh){var t=e.render;if("function"!=typeof t)throw new Error("render property of ForwardRef was not a function");return(0,s.forwardRef)((function(){var e=arguments;return(0,s.createElement)(sh,null,(function(){return t.apply(void 0,e)}))}))}return"function"!=typeof e||e.prototype&&e.prototype.render||e.isReactClass||Object.prototype.isPrototypeOf.call(s.Component,e)?_h(e):function(e,t){if(th())return e;var n,r,o,i=ih({forwardRef:!1},t),a=e.displayName||e.name,l=function(t,n){return oh((function(){return e(t,n)}),a)};return l.displayName=a,n=i.forwardRef?(0,s.memo)((0,s.forwardRef)(l)):(0,s.memo)(l),r=e,o=n,Object.keys(r).forEach((function(e){ah[e]||Object.defineProperty(o,e,Object.getOwnPropertyDescriptor(r,e))})),n.displayName=a,n}(e)}if(!s.Component)throw new Error("mobx-react requires React to be available");if(!Re)throw new Error("mobx-react requires mobx to be available");var Hh,Yh,Gh,Qh=ka(Zc)(Th||(Th=Ao(["\n button {\n background-color: transparent;\n border: 0;\n outline: 0;\n font-size: 13px;\n font-family: ",";\n cursor: pointer;\n padding: 0;\n color: ",";\n &:focus {\n font-weight: ",";\n }\n }\n "," {\n height: ",";\n width: ",";\n polygon {\n fill: ",";\n }\n }\n"])),(function(e){return e.theme.typography.code.fontFamily}),(function(e){return e.theme.colors.text.primary}),(function(e){return e.theme.typography.fontWeightBold}),Hc,(function(e){return e.theme.schema.arrow.size}),(function(e){return e.theme.schema.arrow.size}),(function(e){return e.theme.schema.arrow.color})),Xh=ka.span(Rh||(Rh=Ao(["\n vertical-align: middle;\n font-size: ",";\n line-height: 20px;\n"])),(function(e){return e.theme.typography.code.fontSize})),Kh=ka(Xh)(Ph||(Ph=Ao(["\n color: ",";\n"])),(function(e){return Qr(.1,e.theme.schema.typeNameColor)})),Zh=ka(Xh)(jh||(jh=Ao(["\n color: ",";\n"])),(function(e){return e.theme.schema.typeNameColor})),Jh=ka(Xh)(Lh||(Lh=Ao(["\n color: ",";\n word-break: break-word;\n"])),(function(e){return e.theme.schema.typeTitleColor})),em=Zh,tm=ka(Xh.withComponent("div"))(Nh||(Nh=Ao(["\n color: ",";\n font-size: ",";\n font-weight: normal;\n margin-left: 20px;\n line-height: 1;\n"])),(function(e){return e.theme.schema.requireLabelColor}),(function(e){return e.theme.schema.labelsTextSize})),nm=ka(Xh)(Mh||(Mh=Ao(["\n color: ",";\n font-size: 13px;\n"])),(function(e){return e.theme.colors.warning.main})),rm=ka(Xh)(Dh||(Dh=Ao(["\n color: #0e7c86;\n &::before,\n &::after {\n font-weight: bold;\n }\n"]))),om=ka(Xh)(Fh||(Fh=Ao(["\n border-radius: 2px;\n ",";\n & + & {\n margin-left: 0;\n }\n ",";\n"])),(function(e){var t=e.theme;return"\n background-color: ".concat(Qr(.95,t.colors.text.primary),";\n color: ").concat(Qr(.1,t.colors.text.primary),";\n\n padding: 0 ").concat(t.spacing.unit,"px;\n border: 1px solid ").concat(Qr(.9,t.colors.text.primary),";\n font-family: ").concat(t.typography.code.fontFamily,";\n}")}),Ea("ExampleValue")),im=ka(om)(zh||(zh=Ao([""]))),am=ka(Xh)(Uh||(Uh=Ao(["\n border-radius: 2px;\n ",";\n & + & {\n margin-left: 0;\n }\n ",";\n"])),(function(e){var t=e.theme;return"\n background-color: ".concat(Qr(.95,t.colors.primary.light),";\n color: ").concat(Qr(.1,t.colors.primary.main),";\n\n margin: 0 ").concat(t.spacing.unit,"px;\n padding: 0 ").concat(t.spacing.unit,"px;\n border: 1px solid ").concat(Qr(.9,t.colors.primary.main),";\n font-family: ").concat(t.typography.code.fontFamily,";\n}")}),Ea("ConstraintItem")),sm=ka.button(Bh||(Bh=Ao(["\n background-color: transparent;\n border: 0;\n color: ",";\n margin-left: ","px;\n border-radius: 2px;\n cursor: pointer;\n outline-color: ",";\n font-size: 12px;\n"])),(function(e){return e.theme.colors.text.secondary}),(function(e){return e.theme.spacing.unit}),(function(e){return e.theme.colors.text.secondary})),lm=(r(2479),ka.div(Hh||(Hh=Ao(["\n ",";\n ","\n"])),Qf,(function(e){return e.compact?"":"margin: 1em 0"}))),cm=Wh(Yh=function(e){Eo(n,e);var t=Oo(n);function n(){return nr(this,n),t.apply(this,arguments)}return or(n,[{key:"render",value:function(){var e=this.props.externalDocs;return e&&e.url?s.createElement(lm,{compact:this.props.compact},s.createElement("a",{href:e.url},e.description||e.url)):null}}]),n}(s.Component))||Yh,um=function(e){Eo(r,e);var t=Oo(r);function r(){var e;nr(this,r);for(var o=arguments.length,i=new Array(o),a=0;al,p=l?o?"… ".concat(n.length-l," more"):"Hide":"";return s.createElement("div",null,s.createElement(Xh,null,"array"===r?bo("enumArray"):""," ",1===n.length?bo("enumSingleValue"):bo("enum"),":")," ",c.map((function(e,t){var n=a?e:JSON.stringify(e);return s.createElement(s.Fragment,{key:t},s.createElement(om,null,n)," ")})),u?s.createElement(dm,{onClick:function(){e.toggle()}},p):null)}}]),r}(s.PureComponent);n(um,"contextType",La);var pm,fm,dm=ka.span(Gh||(Gh=Ao(["\n color: ",";\n vertical-align: middle;\n font-size: 13px;\n line-height: 20px;\n padding: 0 5px;\n cursor: pointer;\n"])),(function(e){return e.theme.colors.primary.main})),hm=ka(Xf)(pm||(pm=Ao(["\n margin: 2px 0;\n"]))),mm=function(e){Eo(n,e);var t=Oo(n);function n(){return nr(this,n),t.apply(this,arguments)}return or(n,[{key:"render",value:function(){var e=this.props.extensions;return s.createElement(La.Consumer,null,(function(t){return s.createElement(s.Fragment,null,t.showExtensions&&Object.keys(e).map((function(t){return s.createElement(hm,{key:t},s.createElement(Xh,null," ",t.substring(2),": ")," ",s.createElement(im,null,"string"==typeof e[t]?e[t]:JSON.stringify(e[t])))})))}))}}]),n}(s.PureComponent),vm=function(e){Eo(n,e);var t=Oo(n);function n(){return nr(this,n),t.apply(this,arguments)}return or(n,[{key:"render",value:function(){return 0===this.props.constraints.length?null:s.createElement("span",null," ",this.props.constraints.map((function(e){return s.createElement(am,{key:e}," ",e," ")})))}}]),n}(s.PureComponent),gm=function(e){Eo(n,e);var t=Oo(n);function n(){return nr(this,n),t.apply(this,arguments)}return or(n,[{key:"render",value:function(){if(void 0===this.props.value)return null;var e=this.props.raw?this.props.value:JSON.stringify(this.props.value);return s.createElement("div",null,s.createElement(Xh,null," ",this.props.label," ")," ",s.createElement(om,null,e))}}]),n}(s.PureComponent),ym=function(e){Eo(r,e);var t=Oo(r);function r(){var e;nr(this,r);for(var o=arguments.length,i=new Array(o),a=0;a"," "),p.title&&!c&&s.createElement(Jh,null," (",p.title,") "),s.createElement(vm,{constraints:p.constraints}),p.pattern&&!u&&s.createElement(s.Fragment,null,s.createElement(rm,null,o||p.pattern.length<45?p.pattern:"".concat(p.pattern.substr(0,45),"...")),p.pattern.length>45&&s.createElement(sm,{onClick:this.togglePattern},o?"Hide pattern":"Show pattern")),p.isCircular&&s.createElement(nm,null," ",bo("recursive")," ")),h&&s.createElement("div",null,s.createElement(Yc,{type:"warning"}," ",bo("deprecated")," ")),s.createElement(gm,{raw:v,label:bo("default")+":",value:p.default}),!r&&s.createElement(um,{type:p.type,values:p.enum})," ",g,s.createElement(mm,{extensions:a(a({},n.extensions),p.extensions)}),s.createElement("div",null,s.createElement(id,{compact:!0,source:f})),p.externalDocs&&s.createElement(cm,{externalDocs:p.externalDocs,compact:!0}),r&&r(this.props)||null,n.const&&s.createElement(gm,{label:bo("const")+":",value:n.const})||null)}}]),r}(s.PureComponent);function bm(e){var t=e.field;return t.examples?s.createElement(s.Fragment,null,s.createElement(Xh,null," ",bo("examples"),": "),s.createElement(Km,null,Object.values(t.examples).map((function(e,n){return s.createElement("li",{key:n},s.createElement(om,null,xm(t,e.value))," - ",e.summary||e.description)})))):null}function xm(e,t){return e.in?decodeURIComponent(function(e,t){var r=e.name,o=e.style,i=e.explode,a=void 0!==i&&i,s=e.serializationMime;if(s)switch(e.in){case"path":case"header":return hs(t,s);case"cookie":case"query":return"".concat(r,"=").concat(hs(t,s));default:return console.warn("Unexpected parameter location: "+e.in),""}if(!o)return console.warn("Missing style attribute or content for parameter ".concat(r)),"";switch(e.in){case"path":return function(e,t,r,o){var i=r?"*":"",a="";"label"===t?a=".":"matrix"===t&&(a=";");var s="__redoc_param_name__";return ns.parse("{".concat(a).concat(s).concat(i,"}")).expand(n({},s,o)).replace(/__redoc_param_name__/g,e)}(r,o,a,t);case"query":return function(e,t,n,r){switch(t){case"form":return ds(e,n,r);case"spaceDelimited":return Array.isArray(r)?n?ds(e,n,r):"".concat(e,"=").concat(r.join("%20")):(console.warn("The style spaceDelimited is only applicable to arrays"),"");case"pipeDelimited":return Array.isArray(r)?n?ds(e,n,r):"".concat(e,"=").concat(r.join("|")):(console.warn("The style pipeDelimited is only applicable to arrays"),"");case"deepObject":return!n||Array.isArray(r)||"object"!=typeof r?(console.warn("The style deepObject is only applicable for objects with explode=true"),""):fs(r,e);default:return console.warn("Unexpected style for query: "+t),""}}(r,o,a,t);case"header":return function(e,t,r){switch(e){case"simple":var o=t?"*":"",i="__redoc_param_name__",a=ns.parse("{".concat(i).concat(o,"}"));return decodeURIComponent(a.expand(n({},i,r)));default:return console.warn("Unexpected style for header: "+e),""}}(o,a,t);case"cookie":return function(e,t,n,r){switch(t){case"form":return ds(e,n,r);default:return console.warn("Unexpected style for cookie: "+t),""}}(r,o,a,t);default:return console.warn("Unexpected parameter location: "+e.in),""}}(e,t)):t}n(ym,"contextType",La);var wm,km,Em,Sm,_m,Om,Am,Im,Cm,Tm,Rm,Pm,jm,Lm,Nm,Mm,Dm,Fm,zm,Um,Bm,$m,qm,Vm,Wm,Hm,Ym,Gm,Qm,Xm,Km=ka.ul(fm||(fm=Ao(["\n margin-top: 1em;\n padding-left: 0;\n list-style-position: inside;\n"]))),Zm=ka.div(wm||(wm=Ao(["\n padding-left: ","px;\n"])),(function(e){return 2*e.theme.spacing.unit})),Jm=function(e){Eo(n,e);var t=Oo(n);function n(){return nr(this,n),t.apply(this,arguments)}return or(n,[{key:"render",value:function(){var e=this.props.schema.items,t=ys(function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:void 0,t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:void 0;return{type:"array",minItems:e,maxItems:t}}(e.schema.minItems,e.schema.maxItems));return s.createElement("div",null,s.createElement(au,null," Array (",t,")"),s.createElement(Zm,null,s.createElement(iv,ir({},this.props,{schema:e}))),s.createElement(su,null))}}]),n}(s.PureComponent),ev=Wh(km=function(e){Eo(r,e);var t=Oo(r);function r(){var e;nr(this,r);for(var o=arguments.length,i=new Array(o),a=0;an[t.value]?1:-1}))}}},{key:"render",value:function(){var e=this.props,t=e.parent,n=e.enumValues;if(void 0===t.oneOf)return null;var r=t.oneOf.map((function(e,t){return{value:e.title,idx:t}})),o=r[t.activeOneOf].value;return this.sortOptions(r,n),s.createElement(Hu,{value:o,options:r,onChange:this.changeActiveChild,ariaLabel:"Example"})}}]),r}(s.Component))||Em,nv=Wh((Om=_m=function(e){Eo(n,e);var t=Oo(n);function n(){return nr(this,n),t.apply(this,arguments)}return or(n,[{key:"parentSchema",get:function(){return this.props.discriminator.parentSchema}},{key:"render",value:function(){var e=this,t=this.props,n=t.schema.fields,r=void 0===n?[]:n,o=t.showTitle,i=t.discriminator,a=this.props.skipReadOnly||this.props.skipWriteOnly?r.filter((function(t){return!(e.props.skipReadOnly&&t.schema.readOnly||e.props.skipWriteOnly&&t.schema.writeOnly)})):r,l=this.context.expandSingleSchemaField&&1===a.length;return s.createElement(nu,null,o&&s.createElement(Qc,null,this.props.schema.title),s.createElement("tbody",null,so(a,(function(t,n){return s.createElement(ev,{key:t.name,isLast:n,field:t,expandByDefault:l,renderDiscriminatorSwitch:i&&i.fieldName===t.name&&function(){return s.createElement(tv,{parent:e.parentSchema,enumValues:t.schema.enum})}||void 0,className:t.expanded?"expanded":void 0,showExamples:!1,skipReadOnly:e.props.skipReadOnly,skipWriteOnly:e.props.skipWriteOnly,showTitle:e.props.showTitle})}))))}}]),n}(s.Component),n(_m,"contextType",La),Sm=Om))||Sm,rv=Wh(Am=function(e){Eo(r,e);var t=Oo(r);function r(){var e;nr(this,r);for(var o=arguments.length,i=new Array(o),a=0;a div,\n & > pre {\n padding: ","px;\n margin: 0;\n }\n\n & > div > pre {\n padding: 0;\n }\n"])),(function(e){return e.theme.codeBlock.backgroundColor}),(function(e){return 4*e.theme.spacing.unit})),lv={oauth2:"OAuth2",apiKey:"API Key",http:"HTTP",openIdConnect:"OpenID Connect"},cv=function(e){Eo(n,e);var t=Oo(n);function n(){return nr(this,n),t.apply(this,arguments)}return or(n,[{key:"render",value:function(){var e=this.props,t=e.type,n=e.flow;return s.createElement("tr",null,s.createElement("th",null," ",t," OAuth Flow "),s.createElement("td",null,"implicit"===t||"authorizationCode"===t?s.createElement("div",null,s.createElement("strong",null," Authorization URL: "),n.authorizationUrl):null,"password"===t||"clientCredentials"===t||"authorizationCode"===t?s.createElement("div",null,s.createElement("strong",null," Token URL: "),n.tokenUrl):null,n.refreshUrl&&s.createElement("div",null,s.createElement("strong",null," Refresh URL: "),n.refreshUrl),s.createElement("div",null,s.createElement("strong",null," Scopes: ")),s.createElement("ul",null,Object.keys(n.scopes||{}).map((function(e){return s.createElement("li",{key:e},s.createElement("code",null,e)," - ",s.createElement(id,{inline:!0,source:n.scopes[e]||""}))})))))}}]),n}(s.PureComponent),uv=function(e){Eo(n,e);var t=Oo(n);function n(){return nr(this,n),t.apply(this,arguments)}return or(n,[{key:"render",value:function(){return this.props.securitySchemes.schemes.map((function(e){return s.createElement(tc,{id:e.sectionId,key:e.id},s.createElement(oc,null,s.createElement(ec,null,s.createElement(lc,null,s.createElement(Ec,{to:e.sectionId}),e.id),s.createElement(id,{source:e.description||""}),s.createElement(Xf,null,s.createElement("table",{className:"security-details"},s.createElement("tbody",null,s.createElement("tr",null,s.createElement("th",null," Security Scheme Type "),s.createElement("td",null," ",lv[e.type]||e.type," ")),e.apiKey?s.createElement("tr",null,s.createElement("th",null," ",(t=e.apiKey.in||"").charAt(0).toUpperCase()+t.slice(1)," parameter name:"),s.createElement("td",null," ",e.apiKey.name," ")):e.http?[s.createElement("tr",{key:"scheme"},s.createElement("th",null," HTTP Authorization Scheme "),s.createElement("td",null," ",e.http.scheme," ")),"bearer"===e.http.scheme&&e.http.bearerFormat&&s.createElement("tr",{key:"bearer"},s.createElement("th",null," Bearer format "),s.createElement("td",null,' "',e.http.bearerFormat,'" '))]:e.openId?s.createElement("tr",null,s.createElement("th",null," Connect URL "),s.createElement("td",null,s.createElement("a",{target:"_blank",rel:"noopener noreferrer",href:e.openId.connectUrl},e.openId.connectUrl))):e.flows?Object.keys(e.flows).map((function(t){return s.createElement(cv,{key:t,type:t,flow:e.flows[t]})})):null))))));var t}))}}]),n}(s.PureComponent),pv=function(){function e(t,r){var o=this,i=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{},a=!(arguments.length>3&&void 0!==arguments[3])||arguments[3];nr(this,e),n(this,"menu",void 0),n(this,"spec",void 0),n(this,"rawOptions",void 0),n(this,"options",void 0),n(this,"search",void 0),n(this,"marker",new Fs),n(this,"scroll",void 0),n(this,"disposer",null),this.rawOptions=i,this.options=new ko(i,fv),this.scroll=new qf(this.options),Bf.updateOnHistory(Ms.currentId,this.scroll),this.spec=new Df(t,r,this.options),this.menu=new Bf(this.spec,this.scroll,Ms),this.options.disableSearch||(this.search=new Vf,a&&this.search.indexItems(this.menu.items),this.disposer=Gt(this.menu,"activeItemIdx",(function(e){o.updateMarkOnMenu(e.newValue)})))}var t;return or(e,[{key:"onDidMount",value:function(){this.menu.updateOnHistory(),this.updateMarkOnMenu(this.menu.activeItemIdx)}},{key:"dispose",value:function(){this.scroll.dispose(),this.menu.dispose(),this.search&&this.search.dispose(),null!=this.disposer&&this.disposer()}},{key:"toJS",value:(t=Fa(Ua().mark((function e(){return Ua().wrap((function(e){for(;;)switch(e.prev=e.next){case 0:if(e.t0={activeItemIdx:this.menu.activeItemIdx},e.t1={url:this.spec.parser.specUrl,data:this.spec.parser.spec},!this.search){e.next=8;break}return e.next=5,this.search.toJS();case 5:e.t2=e.sent,e.next=9;break;case 8:e.t2=void 0;case 9:return e.t3=e.t2,e.t4=this.rawOptions,e.abrupt("return",{menu:e.t0,spec:e.t1,searchIndex:e.t3,options:e.t4});case 12:case"end":return e.stop()}}),e,this)}))),function(){return t.apply(this,arguments)})},{key:"updateMarkOnMenu",value:function(e){for(var t=Math.max(0,e),n=Math.min(this.menu.flatItems.length,t+5),r=[],o=t;o1?zr(.1,n.sidebar.backgroundColor):1===e?zr(.05,n.sidebar.backgroundColor):""}var Iv,Cv,Tv,Rv,Pv,jv,Lv,Nv,Mv,Dv,Fv,zv,Uv,Bv=ka.ul(qm||(qm=Ao(["\n margin: 0;\n padding: 0;\n\n & & {\n font-size: 0.929em;\n }\n\n ",";\n"])),(function(e){return e.expanded?"":"display: none;"})),$v=ka.li(Vm||(Vm=Ao(["\n list-style: none inside none;\n overflow: hidden;\n text-overflow: ellipsis;\n padding: 0;\n ",";\n"])),(function(e){return 0===e.depth?"margin-top: 15px":""})),qv={0:ga(Wm||(Wm=Ao(["\n opacity: 0.7;\n text-transform: ",";\n font-size: 0.8em;\n padding-bottom: 0;\n cursor: default;\n color: ",";\n "])),(function(e){return e.theme.sidebar.groupItems.textTransform}),(function(e){return e.theme.sidebar.textColor})),1:ga(Hm||(Hm=Ao(["\n font-size: 0.929em;\n text-transform: ",";\n &:hover {\n color: ",";\n }\n "])),(function(e){return e.theme.sidebar.level1Items.textTransform}),(function(e){return e.theme.sidebar.activeTextColor})),2:ga(Ym||(Ym=Ao(["\n color: ",";\n "])),(function(e){return e.theme.sidebar.textColor}))},Vv=ka.label.attrs((function(e){return{role:"menuitem",className:_v()("-depth"+e.depth,{active:e.active})}}))(Gm||(Gm=Ao(["\n cursor: pointer;\n color: ",";\n margin: 0;\n padding: 12.5px ","px;\n ","\n display: flex;\n justify-content: space-between;\n font-family: ",";\n ",";\n background-color: ",";\n\n ",";\n\n &:hover {\n background-color: ",";\n }\n\n "," {\n height: ",";\n width: ",";\n polygon {\n fill: ",";\n }\n }\n"])),(function(e){return e.active?e.theme.sidebar.activeTextColor:e.theme.sidebar.textColor}),(function(e){return 4*e.theme.spacing.unit}),(function(e){var t=e.depth,n=e.type,r=e.theme;return"section"===n&&t>1&&"padding-left: "+8*r.spacing.unit+"px;"||""}),(function(e){return e.theme.typography.headings.fontFamily}),(function(e){return qv[e.depth]}),(function(e){return e.active?Av(e.depth,e):""}),(function(e){return e.deprecated&&Gc||""}),(function(e){return Av(e.depth,e)}),Hc,(function(e){return e.theme.sidebar.arrow.size}),(function(e){return e.theme.sidebar.arrow.size}),(function(e){return e.theme.sidebar.arrow.color})),Wv=ka.span(Qm||(Qm=Ao(["\n display: inline-block;\n vertical-align: middle;\n width: ",";\n overflow: hidden;\n text-overflow: ellipsis;\n"])),(function(e){return e.width?e.width:"auto"})),Hv=ka.div(Xm||(Xm=Ao(["\n ",";\n"])),(function(e){var t=e.theme;return"\n font-size: 0.8em;\n margin-top: ".concat(2*t.spacing.unit,"px;\n padding: 0 ").concat(4*t.spacing.unit,"px;\n text-align: left;\n\n opacity: 0.7;\n\n a,\n a:visited,\n a:hover {\n color: ").concat(t.sidebar.textColor," !important;\n border-top: 1px solid ").concat(zr(.1,t.sidebar.backgroundColor),";\n padding: ").concat(t.spacing.unit,"px 0;\n display: block;\n }\n")})),Yv=function(e){Eo(n,e);var t=Oo(n);function n(){return nr(this,n),t.apply(this,arguments)}return or(n,[{key:"render",value:function(){var e=this.props,t=e.name,n=e.opened,r=e.className,o=e.onClick,i=e.httpVerb,a=e.deprecated;return s.createElement(Gv,{className:r,onClick:o||void 0},s.createElement(Xv,{type:i},Os(i)),s.createElement(Hc,{size:"1.5em",direction:n?"down":"right",float:"left"}),s.createElement(Qv,{deprecated:a},t),a?s.createElement(Yc,{type:"warning"}," ",bo("deprecated")," "):null)}}]),n}(s.PureComponent),Gv=ka.button(Iv||(Iv=Ao(["\n border: 0;\n width: 100%;\n text-align: left;\n & > * {\n vertical-align: middle;\n }\n\n "," {\n polygon {\n fill: ",";\n }\n }\n"])),Hc,(function(e){var t=e.theme;return zr(t.colors.tonalOffset,t.colors.gray[100])})),Qv=ka.span(Cv||(Cv=Ao(["\n text-decoration: ",";\n margin-right: 8px;\n"])),(function(e){return e.deprecated?"line-through":"none"})),Xv=ka(Ov)(Tv||(Tv=Ao(["\n margin: 0px 5px 0px 0px;\n"]))),Kv=ka(Yv)(Rv||(Rv=Ao(["\n padding: 10px;\n border-radius: 2px;\n margin-bottom: 4px;\n line-height: 1.5em;\n background-color: ",";\n cursor: pointer;\n outline-color: ",";\n"])),(function(e){return e.theme.colors.gray[100]}),(function(e){var t=e.theme;return zr(t.colors.tonalOffset,t.colors.gray[100])})),Zv=ka.div(Pv||(Pv=Ao(["\n padding: 10px 25px;\n background-color: ",";\n margin-bottom: 5px;\n margin-top: 5px;\n"])),(function(e){return e.theme.colors.gray[50]})),Jv=function(e){Eo(r,e);var t=Oo(r);function r(){var e;nr(this,r);for(var o=arguments.length,i=new Array(o),a=0;a span {\n color: ",";\n }\n"])),(function(e){return e.theme.colors.primary.main}),(function(e){return e.theme.colors.text.primary})),sg=function(e){Eo(r,e);var t=Oo(r);function r(e){var o;return nr(this,r),n(ar(o=t.call(this,e)),"toggle",(function(){o.setState({expanded:!o.state.expanded})})),o.state={expanded:!1},o}return or(r,[{key:"render",value:function(){var e=this,t=this.props,n=t.operation,r=t.inverted,o=t.hideHostname,i=this.state.expanded;return s.createElement(La.Consumer,null,(function(t){return s.createElement(eg,null,s.createElement(ng,{onClick:e.toggle,expanded:i,inverted:r},s.createElement(rg,{type:n.httpVerb,compact:e.props.compact},n.httpVerb),s.createElement(tg,null,n.path),s.createElement(Hc,{float:"right",color:r?"black":"white",size:"20px",direction:i?"up":"down",style:{marginRight:"-25px"}})),s.createElement(og,{expanded:i,"aria-hidden":!i},n.servers.map((function(e){var r=t.expandDefaultServerVariables?function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};return e.replace(/(?:{)([\w-.]+)(?:})/g,(function(e,n){return t[n]&&t[n].default||e}))}(e.url,e.variables):e.url,i=function(e){try{return mo(e).pathname}catch(t){return e}}(r);return s.createElement(ig,{key:r},s.createElement(id,{source:e.description||"",compact:!0}),s.createElement(Jv,null,s.createElement(ag,null,s.createElement("span",null,o||t.hideHostname?"/"===i?"":i:r),n.path)))}))))}))}}]),r}(s.Component),lg=function(e){Eo(n,e);var t=Oo(n);function n(){return nr(this,n),t.apply(this,arguments)}return or(n,[{key:"render",value:function(){var e=this.props,t=e.place,n=e.parameters;return n&&n.length?s.createElement("div",{key:t},s.createElement(uc,null,t," Parameters"),s.createElement(nu,null,s.createElement("tbody",null,so(n,(function(e,t){return s.createElement(ev,{key:e.name,isLast:t,field:e,showExamples:!0})}))))):null}}]),n}(s.PureComponent),cg=Wh(Uv=function(e){Eo(r,e);var t=Oo(r);function r(){var e;nr(this,r);for(var o=arguments.length,i=new Array(o),a=0;a0?ug:[],a=t&&t.content,l=t&&t.description;return s.createElement(s.Fragment,null,i.map((function(e){return s.createElement(lg,{key:e,place:e,parameters:o[e]})})),a&&s.createElement(dg,{content:a,description:l}))}}]),n}(s.PureComponent);function fg(e){return s.createElement(uc,{key:"header"},"Request Body schema: ",s.createElement(Wf,e))}function dg(e){var t=e.content,n=e.description;return s.createElement(cg,{content:t,renderDropdown:fg},(function(e){var t=e.schema;return s.createElement(s.Fragment,null,void 0!==n&&s.createElement(id,{source:n}),s.createElement(iv,{skipReadOnly:!0,key:"schema",schema:t}))}))}var hg,mg,vg,gg,yg,bg,xg,wg,kg,Eg,Sg,_g,Og,Ag,Ig,Cg,Tg,Rg,Pg,jg,Lg,Ng,Mg,Dg,Fg,zg,Ug,Bg,$g,qg,Vg,Wg,Hg,Yg,Gg,Qg,Xg,Kg,Zg,Jg,ey,ty,ny,ry,oy,iy,ay,sy,ly,cy,uy,py,fy=ka(function(e){Eo(n,e);var t=Oo(n);function n(){return nr(this,n),t.apply(this,arguments)}return or(n,[{key:"render",value:function(){var e=this.props,t=e.title,n=e.type,r=e.empty,o=e.code,i=e.opened,a=e.className,l=e.onClick;return s.createElement("button",{className:a,onClick:!r&&l||void 0,"aria-expanded":i,disabled:r},!r&&s.createElement(Hc,{size:"1.5em",color:n,direction:i?"down":"right",float:"left"}),s.createElement(my,null,o," "),s.createElement(id,{compact:!0,inline:!0,source:t}))}}]),n}(s.PureComponent))(hg||(hg=Ao(["\n display: block;\n border: 0;\n width: 100%;\n text-align: left;\n padding: 10px;\n border-radius: 2px;\n margin-bottom: 4px;\n line-height: 1.5em;\n background-color: #f2f2f2;\n cursor: pointer;\n\n color: ",";\n background-color: ",";\n &:focus {\n outline: auto;\n outline-color: ",";\n }\n ",";\n"])),(function(e){return e.theme.colors.responses[e.type].color}),(function(e){return e.theme.colors.responses[e.type].backgroundColor}),(function(e){return e.theme.colors.responses[e.type].color}),(function(e){return e.empty?'\ncursor: default;\n&::before {\n content: "—";\n font-weight: bold;\n width: 1.5em;\n text-align: center;\n display: inline-block;\n vertical-align: top;\n}\n&:focus {\n outline: 0;\n}\n':""})),dy=ka.div(mg||(mg=Ao(["\n padding: 10px;\n"]))),hy=ka(uc.withComponent("caption"))(vg||(vg=Ao(["\n text-align: left;\n margin-top: 1em;\n caption-side: top;\n"]))),my=ka.strong(gg||(gg=Ao(["\n vertical-align: top;\n"]))),vy=function(e){Eo(n,e);var t=Oo(n);function n(){return nr(this,n),t.apply(this,arguments)}return or(n,[{key:"render",value:function(){var e=this.props.headers;return void 0===e||0===e.length?null:s.createElement(nu,null,s.createElement(hy,null," Response Headers "),s.createElement("tbody",null,so(e,(function(e,t){return s.createElement(ev,{isLast:t,key:e.name,field:e,showExamples:!0})}))))}}]),n}(s.PureComponent),gy=function(e){Eo(r,e);var t=Oo(r);function r(){var e;nr(this,r);for(var o=arguments.length,i=new Array(o),a=0;a0&&" (",e.scopes.map((function(e){return s.createElement(wy,{key:e},e)})),e.scopes.length>0&&") ")})))}}]),n}(s.PureComponent),_y=ka.div(Eg||(Eg=Ao(["\n flex: 1 1 auto;\n"]))),Oy=ka.div(Sg||(Sg=Ao(["\n width: ",";\n ","\n"])),(function(e){return e.theme.schema.defaultDetailsWidth}),wa("small")(_g||(_g=Ao(["\n margin-top: 10px;\n "])))),Ay=ka(uc)(Og||(Og=Ao(["\n display: inline-block;\n margin: 0;\n"]))),Iy=ka.div(Ag||(Ag=Ao(["\n width: 100%;\n display: flex;\n margin: 1em 0;\n\n ","\n"])),wa("small")(Ig||(Ig=Ao(["\n flex-direction: column;\n "])))),Cy=function(e){Eo(n,e);var t=Oo(n);function n(){return nr(this,n),t.apply(this,arguments)}return or(n,[{key:"render",value:function(){var e=this.props.securities;return e.length?s.createElement(Iy,null,s.createElement(_y,null,s.createElement(Ay,null,"Authorizations: ")),s.createElement(Oy,null,e.map((function(e,t){return s.createElement(Sy,{key:t,security:e})})))):null}}]),n}(s.PureComponent),Ty=Wh(Cg=function(e){Eo(n,e);var t=Oo(n);function n(){return nr(this,n),t.apply(this,arguments)}return or(n,[{key:"render",value:function(){var e=this.props.operation,t=e.description,n=e.externalDocs,r=!(!t&&!n);return s.createElement(Zv,null,r&&s.createElement(Ry,null,void 0!==t&&s.createElement(id,{source:t}),n&&s.createElement(cm,{externalDocs:n})),s.createElement(sg,{operation:this.props.operation,inverted:!0,compact:!0}),s.createElement(mm,{extensions:e.extensions}),s.createElement(Cy,{securities:e.security}),s.createElement(pg,{parameters:e.parameters,body:e.requestBody}),s.createElement(xy,{responses:e.responses,isCallback:e.isCallback}))}}]),n}(s.Component))||Cg,Ry=ka.div(Tg||(Tg=Ao(["\n margin-bottom: ","px;\n"])),(function(e){return 3*e.theme.spacing.unit})),Py=Wh(Rg=function(e){Eo(r,e);var t=Oo(r);function r(){var e;nr(this,r);for(var o=arguments.length,i=new Array(o),a=0;a0})))return null;var r=n.map((function(e,t){return{value:"".concat(e.httpVerb.toUpperCase(),": ").concat(e.name),idx:t}}));return s.createElement("div",null,s.createElement(cc,null," Callback payload samples "),s.createElement(Uy,null,s.createElement(Ny,{items:n,renderDropdown:this.renderDropdown,label:"Callback",options:r},(function(t){return s.createElement(Dy,{key:"callbackPayloadSample",callback:t,renderDropdown:e.renderDropdown})}))))}}]),r}(s.Component),n(Dg,"contextType",La),Mg=Fg))||Mg,Uy=ka.div(zg||(zg=Ao(["\n background: ",";\n padding: ","px;\n"])),(function(e){return e.theme.codeBlock.backgroundColor}),(function(e){return 4*e.theme.spacing.unit})),By=Wh(($g=Bg=function(e){Eo(r,e);var t=Oo(r);function r(){var e;nr(this,r);for(var o=arguments.length,i=new Array(o),a=0;a0,n=1===e.length&&this.context.hideSingleRequestSampleTab;return t&&s.createElement("div",null,s.createElement(cc,null," Request samples "),s.createElement(Pp,{defaultIndex:0},s.createElement(yp,{hidden:n},e.map((function(e){return s.createElement(kp,{key:e.lang+"_"+(e.label||"")},void 0!==e.label?e.label:e.lang)}))),e.map((function(e){return s.createElement(Ap,{key:e.lang+"_"+(e.label||"")},Ml(e)?s.createElement("div",null,s.createElement(My,{content:e.requestBodyContent})):s.createElement(jd,{lang:e.lang,source:e.source}))}))))||null}}]),r}(s.Component),n(Bg,"contextType",La),Ug=$g))||Ug,$y=Wh(qg=function(e){Eo(r,e);var t=Oo(r);function r(){var e;nr(this,r);for(var o=arguments.length,i=new Array(o),a=0;a0&&s.createElement("div",null,s.createElement(cc,null," Response samples "),s.createElement(Pp,{defaultIndex:0},s.createElement(yp,null,e.map((function(e){return s.createElement(kp,{className:"tab-"+e.type,key:e.code},e.code)}))),e.map((function(e){return s.createElement(Ap,{key:e.code},s.createElement("div",null,s.createElement(My,{content:e.content})))}))))||null}}]),r}(s.Component))||qg,qy=ka(oc)(Vg||(Vg=Ao(["\n backface-visibility: hidden;\n contain: content;\n overflow: hidden;\n"]))),Vy=ka.div(Wg||(Wg=Ao(["\n margin-bottom: ","px;\n"])),(function(e){return 6*e.theme.spacing.unit})),Wy=Wh(Hg=function(e){Eo(n,e);var t=Oo(n);function n(){return nr(this,n),t.apply(this,arguments)}return or(n,[{key:"render",value:function(){var e=this.props.operation,t=e.name,n=e.description,r=e.deprecated,o=e.externalDocs,i=e.isWebhook,a=!(!n&&!o);return s.createElement(La.Consumer,null,(function(l){return s.createElement(qy,null,s.createElement(ec,null,s.createElement(lc,null,s.createElement(Ec,{to:e.id}),t," ",r&&s.createElement(Yc,{type:"warning"}," Deprecated "),i&&s.createElement(Yc,{type:"primary"}," Webhook ")),l.pathInMiddlePanel&&!i&&s.createElement(sg,{operation:e,inverted:!0}),a&&s.createElement(Vy,null,void 0!==n&&s.createElement(id,{source:n}),o&&s.createElement(cm,{externalDocs:o})),s.createElement(mm,{extensions:e.extensions}),s.createElement(Cy,{securities:e.security}),s.createElement(pg,{parameters:e.parameters,body:e.requestBody}),s.createElement(xy,{responses:e.responses}),s.createElement(jy,{callbacks:e.callbacks})),s.createElement(rc,null,!l.pathInMiddlePanel&&!i&&s.createElement(sg,{operation:e}),s.createElement(By,{operation:e}),s.createElement($y,{operation:e}),s.createElement(zy,{callbacks:e.callbacks})))}))}}]),n}(s.Component))||Hg,Hy=Wh(Yg=function(e){Eo(n,e);var t=Oo(n);function n(){return nr(this,n),t.apply(this,arguments)}return or(n,[{key:"render",value:function(){var e=this.props.items;return 0===e.length?null:e.map((function(e){return s.createElement(Yy,{key:e.id,item:e})}))}}]),n}(s.Component))||Yg,Yy=Wh(Gg=function(e){Eo(n,e);var t=Oo(n);function n(){return nr(this,n),t.apply(this,arguments)}return or(n,[{key:"render",value:function(){var e,t=this.props.item;switch(t.type){case"group":e=null;break;case"tag":case"section":e=s.createElement(Qy,this.props);break;case"operation":e=s.createElement(Xy,{item:t});break;default:e=s.createElement(Qy,this.props)}return s.createElement(s.Fragment,null,e&&s.createElement(tc,{id:t.id,underlined:"operation"===t.type},e),t.items&&s.createElement(Hy,{items:t.items}))}}]),n}(s.Component))||Gg,Gy=function(e){return s.createElement(ec,{compact:!0},e)},Qy=Wh(Qg=function(e){Eo(n,e);var t=Oo(n);function n(){return nr(this,n),t.apply(this,arguments)}return or(n,[{key:"render",value:function(){var e=this.props.item,t=e.name,n=e.description,r=e.externalDocs,o=2===e.level?lc:sc;return s.createElement(s.Fragment,null,s.createElement(oc,null,s.createElement(ec,{compact:!1},s.createElement(o,null,s.createElement(Ec,{to:this.props.item.id}),t))),s.createElement(Ev,{source:n||"",htmlWrap:Gy}),r&&s.createElement(oc,null,s.createElement(ec,null,s.createElement(cm,{externalDocs:r}))))}}]),n}(s.Component))||Qg,Xy=Wh(Xg=function(e){Eo(n,e);var t=Oo(n);function n(){return nr(this,n),t.apply(this,arguments)}return or(n,[{key:"render",value:function(){return s.createElement(Wy,{operation:this.props.item})}}]),n}(s.Component))||Xg,Ky=Wh(Kg=function(e){Eo(r,e);var t=Oo(r);function r(){var e;nr(this,r);for(var o=arguments.length,i=new Array(o),a=0;a0&&t.items.length>0&&s.createElement(Hc,{float:"right",direction:t.expanded?"down":"right"})||null),!n&&t.items&&t.items.length>0&&s.createElement(Jy,{expanded:t.expanded,items:t.items,onActivate:this.props.onActivate}))}}]),r}(s.Component))||Kg,Zy=Wh(Zg=function(e){Eo(r,e);var t=Oo(r);function r(){var e;nr(this,r);for(var o=arguments.length,i=new Array(o),a=0;a.5?zr:Vr)(.1,t.sidebar.backgroundColor)}),(function(e){return e.theme.typography.fontFamily}),(function(e){return e.theme.sidebar.textColor})),_b=ka((function(e){return s.createElement("svg",{className:e.className,version:"1.1",viewBox:"0 0 1000 1000",x:"0px",xmlns:"http://www.w3.org/2000/svg",y:"0px"},s.createElement("path",{d:"M968.2,849.4L667.3,549c83.9-136.5,66.7-317.4-51.7-435.6C477.1-25,252.5-25,113.9,113.4c-138.5,138.3-138.5,362.6,0,501C219.2,730.1,413.2,743,547.6,666.5l301.9,301.4c43.6,43.6,76.9,14.9,104.2-12.4C981,928.3,1011.8,893,968.2,849.4z M524.5,522c-88.9,88.7-233,88.7-321.8,0c-88.9-88.7-88.9-232.6,0-321.3c88.9-88.7,233-88.7,321.8,0C613.4,289.4,613.4,433.3,524.5,522z"}))})).attrs({className:"search-icon"})(pb||(pb=Ao(["\n position: absolute;\n left: ","px;\n height: 1.8em;\n width: 0.9em;\n\n path {\n fill: ",";\n }\n"])),(function(e){return 4*e.theme.spacing.unit}),(function(e){return e.theme.sidebar.textColor})),Ob=ka.div(fb||(fb=Ao(["\n padding: ","px 0;\n background-color: ","};\n color: ",";\n min-height: 150px;\n max-height: 250px;\n border-top: ","};\n border-bottom: ","};\n margin-top: 10px;\n line-height: 1.4;\n font-size: 0.9em;\n \n li {\n background-color: inherit;\n }\n\n "," {\n padding-top: 6px;\n padding-bottom: 6px;\n\n &:hover,\n &.active {\n background-color: ",";\n }\n\n > svg {\n display: none;\n }\n }\n"])),(function(e){return e.theme.spacing.unit}),(function(e){var t=e.theme;return zr(.05,t.sidebar.backgroundColor)}),(function(e){return e.theme.sidebar.textColor}),(function(e){var t=e.theme;return zr(.1,t.sidebar.backgroundColor)}),(function(e){var t=e.theme;return zr(.1,t.sidebar.backgroundColor)}),Vv,(function(e){var t=e.theme;return zr(.1,t.sidebar.backgroundColor)})),Ab=ka.i(db||(db=Ao(["\n position: absolute;\n display: inline-block;\n width: ","px;\n text-align: center;\n right: ","px;\n line-height: 2em;\n vertical-align: middle;\n margin-right: 2px;\n cursor: pointer;\n font-style: normal;\n color: '#666';\n"])),(function(e){return 2*e.theme.spacing.unit}),(function(e){return 4*e.theme.spacing.unit})),Ib=(hb=(0,Ga.debounce)(400),Ya((mb=function(e){Eo(r,e);var t=Oo(r);function r(e){var o;return nr(this,r),n(ar(o=t.call(this,e)),"activeItemRef",null),n(ar(o),"clear",(function(){o.setState({results:[],term:"",activeItemIdx:-1}),o.props.marker.unmark()})),n(ar(o),"handleKeyDown",(function(e){if(27===e.keyCode&&o.clear(),40===e.keyCode&&(o.setState({activeItemIdx:Math.min(o.state.activeItemIdx+1,o.state.results.length-1)}),e.preventDefault()),38===e.keyCode&&(o.setState({activeItemIdx:Math.max(0,o.state.activeItemIdx-1)}),e.preventDefault()),13===e.keyCode){var t=o.state.results[o.state.activeItemIdx];if(t){var n=o.props.getItemById(t.meta);n&&o.props.onActivate(n)}}})),n(ar(o),"search",(function(e){var t=e.target.value;t.length<3?o.clearResults(t):o.setState({term:t},(function(){return o.searchCallback(o.state.term)}))})),o.state={results:[],term:"",activeItemIdx:-1},o}return or(r,[{key:"clearResults",value:function(e){this.setState({results:[],term:e}),this.props.marker.unmark()}},{key:"setResults",value:function(e,t){this.setState({results:e}),this.props.marker.mark(t)}},{key:"searchCallback",value:function(e){var t=this;this.props.search.search(e).then((function(n){t.setResults(n,e)}))}},{key:"render",value:function(){var e=this,t=this.state.activeItemIdx,n=this.state.results.map((function(t){return{item:e.props.getItemById(t.meta),score:t.score}}));return n.sort((function(e,t){return t.score-e.score})),s.createElement(Eb,{role:"search"},this.state.term&&s.createElement(Ab,{onClick:this.clear},"×"),s.createElement(_b,null),s.createElement(Sb,{value:this.state.term,onKeyDown:this.handleKeyDown,placeholder:"Search...","aria-label":"Search",type:"text",onChange:this.search}),n.length>0&&s.createElement(wf,{options:{wheelPropagation:!1}},s.createElement(Ob,{"data-role":"search:results"},n.map((function(n,r){return s.createElement(Ky,{item:Object.create(n.item,{active:{value:r===t}}),onActivate:e.props.onActivate,withoutChildren:!0,key:n.item.id,"data-role":"search:result"})})))))}}]),r}(s.PureComponent)).prototype,"searchCallback",[Ga.bind,hb],Object.getOwnPropertyDescriptor(mb.prototype,"searchCallback"),mb.prototype),mb),Cb=function(e){Eo(n,e);var t=Oo(n);function n(){return nr(this,n),t.apply(this,arguments)}return or(n,[{key:"componentDidMount",value:function(){this.props.store.onDidMount()}},{key:"componentWillUnmount",value:function(){this.props.store.dispose()}},{key:"render",value:function(){var e=this.props.store,t=e.spec,n=e.menu,r=e.options,o=e.search,i=e.marker,a=this.props.store;return s.createElement(xa,{theme:r.theme},s.createElement(mc,{value:a},s.createElement(Na,{value:r},s.createElement(xb,{className:"redoc-wrap"},s.createElement(bb,{menu:n,className:"menu-content"},s.createElement(kv,{info:t.info}),!r.disableSearch&&s.createElement(Ib,{search:o,marker:i,getItemById:n.getItemById,onActivate:n.activateAndScroll})||null,s.createElement(eb,{menu:n})),s.createElement(wb,{className:"api-content"},s.createElement(yv,{store:a}),s.createElement(Hy,{items:n.items})),s.createElement(kb,null)))))}}]),n}(s.Component);n(Cb,"propTypes",{store:ja.instanceOf(pv).isRequired});var Tb=function(e){var t=e.spec,n=e.specUrl,r=e.options,o=void 0===r?{}:r,i=e.onLoaded,a=void 0!==o.hideLoading,l=new ko(o);return s.createElement(Ia,null,s.createElement(gc,{spec:t,specUrl:n,options:o,onLoaded:i},(function(e){var t=e.loading,n=e.store;return t?a?null:s.createElement(Pa,{color:l.theme.colors.primary.main}):s.createElement(Cb,{store:n})})))};Ft({useProxies:"ifavailable"});var Rb="2.0.0-rc.55",Pb="68690d18";function jb(e){var t=function(e){for(var t={},n=e.attributes,r=0;r1&&void 0!==arguments[1]?arguments[1]:{},o=arguments.length>2&&void 0!==arguments[2]?arguments[2]:eo("redoc"),i=arguments.length>3?arguments[3]:void 0;if(null===o)throw new Error('"element" argument is not provided and tag is not found on the page');"string"==typeof e?t=e:"object"==typeof e&&(n=e),(0,l.render)(s.createElement(Tb,{spec:n,onLoaded:i,specUrl:t,options:a(a({},r),jb(o))},["Loading..."]),o)}function Nb(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:eo("redoc"),n=arguments.length>2?arguments[2]:void 0,r=pv.fromJS(e);setTimeout((function(){(0,l.hydrate)(s.createElement(Cb,{store:r}),t,n)}),0)}!function(){var e=eo("redoc");if(e){var t=e.getAttribute("spec-url");t&&Lb(t,{},e)}}()}(),o}()}));
+//# sourceMappingURL=redoc.standalone.js.map
- Owncast (0.0.7) Download OpenAPI specification:Download
Owncast is a self-hosted live video and web chat server for use with existing popular broadcasting software. The following APIs represent the state in the development branch.
-
AdminBasicAuthThe username for admin basic auth is admin and the password is the stream key.
-
Security Scheme Type HTTP HTTP Authorization Scheme basic
AccessToken3rd party integration auth where a service user must provide an access token.
-
Security Scheme Type HTTP HTTP Authorization Scheme bearer
Admin operations requiring authentication.
-
Server status and broadcaster Responses 200 Server status and broadcaster details
- Response samples Content type application/json
Copy
Expand all Collapse all { "broadcaster" :
{ "remoteAddr" : "172.217.164.110" ,
"time" : "2020-10-06T23:20:44.588649-07:00" ,
} , "online" : true ,
"viewerCount" : 3 ,
"overallPeakViewerCount" : 4 ,
"sessionPeakViewerCount" : 4 ,
"versionNumber" : "0.0.3"
} Disconnect Broadcaster post /api/admin/disconnect Disconnect the active inbound stream, if one exists, and terminate the broadcast.
-
Responses 200 Operation Success/Failure Response
- Response samples Content type application/json
Example Operation succeeded.
Operation failed.
Copy
Expand all Collapse all Reset your YP registration key. Used when there is a problem with your registration to the Owncast Directory via the YP APIs. This will reset your local registration key.
-
Responses 200 Operation Success/Failure Response
- Response samples Content type application/json
Example Operation succeeded.
Operation failed.
Copy
Expand all Collapse all Return a list of currently connected clients Return a list of currently connected clients with optional geo details.
-
Responses 200 Successful response of an array of clients
- Response samples Content type application/json
Copy
Expand all Collapse all [ { "connectedAt" : "2020-10-06T23:20:44.588649-07:00" ,
"messageCount" : 3 ,
"userAgent" : "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.89 Safari/537.36" ,
"ipAddress" : "172.217.164.110" ,
"username" : "coolperson42" ,
"clientID" : "2ba20dd34f911c198df3218ddc64c740" ,
} ] Return recent log entries Responses 200 Response of server log entries
- Response samples Content type application/json
Copy
Expand all Collapse all [ { "message" : "RTMP server is listening for incoming stream on port: 1935" ,
"level" : "info" ,
"time" : "2020-10-29T18:35:35.011823-07:00"
} ] Return recent warning and error logs. get /api/admin/logs/warnings Return recent warning and error logs.
-
Responses 200 Response of server log entries
- Response samples Content type application/json
Copy
Expand all Collapse all [ { "message" : "RTMP server is listening for incoming stream on port: 1935" ,
"level" : "info" ,
"time" : "2020-10-29T18:35:35.011823-07:00"
} ] Server Configuration get /api/admin/serverconfig Get the current configuration of the Owncast server.
-
Response samples Content type application/json
Copy
Expand all Collapse all { "instanceDetails" :
{ "name" : "string" ,
"summary" : "string" ,
"logo" : "string" ,
"extraPageContent" : "<p>This page is <strong>super</strong> cool!" ,
"version" : "Owncast v0.0.3-macOS (ef3796a033b32a312ebf5b334851cbf9959e7ecb)"
} , "ffmpegPath" : "string" ,
"webServerPort" : 0 ,
"rtmpServerPort" : 0 ,
"videoSettings" :
{ "videoQualityVariants" :
[ { "videoPassthrough" : true ,
"audioPassthrough" : true ,
"videoBitrate" : 0 ,
"audioBitrate" : 0 ,
"scaledWidth" : 0 ,
"scaledHeight" : 0 ,
"framerate" : 0 ,
"cpuUsageLevel" : 0
} ] , "latencyLevel" : 0
} , "yp" :
{ "enabled" : false ,
"instanceUrl" : "string"
} } Chat messages, unfiltered. get /api/admin/chat/messages Get a list of all chat messages with no filters applied.
-
Response samples Content type application/json
Copy
Expand all Collapse all Update the visibility of chat messages. post /api/admin/chat/updatemessagevisibility
/api/admin/chat/updatemessagevisibility
Pass an array of IDs you want to change the chat visibility of.
-
Request Body schema: application/json
visible boolean
Are these messages visible.
-
idArray
Responses 200 Operation Success/Failure Response
- Request samples Content type application/json
Copy
Expand all Collapse all Response samples Content type application/json
Example Operation succeeded.
Operation failed.
Copy
Expand all Collapse all Set the stream key. post /api/admin/config/key Set the stream key. Also used as the admin password.
-
Request Body schema: application/json
value string or integer or object or boolean
Responses 200 Operation Success/Failure Response
- Request samples Content type application/json
Copy
Expand all Collapse all Response samples Content type application/json
Example Operation succeeded.
Operation failed.
Copy
Expand all Collapse all Set the custom page content. post /api/admin/config/pagecontent
/api/admin/config/pagecontent
Set the custom page content using markdown.
-
Request Body schema: application/json
value string or integer or object or boolean
Responses 200 Operation Success/Failure Response
- Request samples Content type application/json
Copy
Expand all Collapse all "# Welcome to my cool server!<br><br>I _hope_ you enjoy it."
Response samples Content type application/json
Example Operation succeeded.
Operation failed.
Copy
Expand all Collapse all Set the stream title. post /api/admin/config/streamtitle
/api/admin/config/streamtitle
Set the title of the currently streaming content.
-
Request Body schema: application/json
value string or integer or object or boolean
Responses 200 Operation Success/Failure Response
- Request samples Content type application/json
Copy
Expand all Collapse all Response samples Content type application/json
Example Operation succeeded.
Operation failed.
Copy
Expand all Collapse all Set the server name. post /api/admin/config/name Set the name associated with your server. Often is your name, username or identity.
-
Request Body schema: application/json
value string or integer or object or boolean
Responses 200 Operation Success/Failure Response
- Request samples Content type application/json
Copy
Expand all Collapse all Response samples Content type application/json
Example Operation succeeded.
Operation failed.
Copy
Expand all Collapse all Set the server summary. post /api/admin/config/serversummary
/api/admin/config/serversummary
Set the summary of your server's streaming content.
-
Request Body schema: application/json
value string or integer or object or boolean
Responses 200 Operation Success/Failure Response
- Request samples Content type application/json
Copy
Expand all Collapse all Response samples Content type application/json
Example Operation succeeded.
Operation failed.
Copy
Expand all Collapse all Set the server logo. post /api/admin/config/logo Set the logo for your server. Path is relative to webroot.
-
Request Body schema: application/json
value string or integer or object or boolean
Responses 200 Operation Success/Failure Response
- Request samples Content type application/json
Copy
Expand all Collapse all Response samples Content type application/json
Example Operation succeeded.
Operation failed.
Copy
Expand all Collapse all Set the server tags. post /api/admin/config/tags Set the tags displayed for your server and the categories you can show up in on the directory.
-
Request Body schema: application/json
value string or integer or object or boolean
Responses 200 Operation Success/Failure Response
- Request samples Content type application/json
Copy
Expand all Collapse all { "value" :
[ "games" ,
"music" ,
"streaming"
] } Response samples Content type application/json
Example Operation succeeded.
Operation failed.
Copy
Expand all Collapse all Set the ffmpeg binary path post /api/admin/config/ffmpegpath
/api/admin/config/ffmpegpath
Set the path for a specific copy of ffmpeg on your system.
-
Request Body schema: application/json
value string or integer or object or boolean
Responses 200 Operation Success/Failure Response
- Request samples Content type application/json
Copy
Expand all Collapse all Response samples Content type application/json
Example Operation succeeded.
Operation failed.
Copy
Expand all Collapse all Set the owncast web port. post /api/admin/config/webserverport
/api/admin/config/webserverport
Set the port the owncast web server should listen on.
-
Request Body schema: application/json
value string or integer or object or boolean
Responses 200 Operation Success/Failure Response
- Request samples Content type application/json
Copy
Expand all Collapse all Response samples Content type application/json
Example Operation succeeded.
Operation failed.
Copy
Expand all Collapse all Set the inbound rtmp server port. post /api/admin/config/rtmpserverport
/api/admin/config/rtmpserverport
Set the port where owncast service will listen for inbound broadcasts.
-
Request Body schema: application/json
value string or integer or object or boolean
Responses 200 Operation Success/Failure Response
- Request samples Content type application/json
Copy
Expand all Collapse all Response samples Content type application/json
Example Operation succeeded.
Operation failed.
Copy
Expand all Collapse all Mark if your stream is not safe for work post /api/admin/config/nsfw Mark if your stream can be consitered not safe for work. Used in different contexts, including the directory for filtering purposes.
-
Request Body schema: application/json
value string or integer or object or boolean
Responses 200 Operation Success/Failure Response
- Request samples Content type application/json
Copy
Expand all Collapse all Response samples Content type application/json
Example Operation succeeded.
Operation failed.
Copy
Expand all Collapse all Set if this server supports the Owncast directory. post /api/admin/config/directoryenabled
/api/admin/config/directoryenabled
If set to true the server will attempt to register itself with the Owncast Directory . Off by default.
-
Request Body schema: application/json
value string or integer or object or boolean
Responses 200 Operation Success/Failure Response
- Request samples Content type application/json
Copy
Expand all Collapse all Response samples Content type application/json
Example Operation succeeded.
Operation failed.
Copy
Expand all Collapse all Set the public url of this owncast server. post /api/admin/config/serverurl
/api/admin/config/serverurl
Set the public url of this owncast server. Used for the directory and optional integrations.
-
Request Body schema: application/json
value string or integer or object or boolean
Responses 200 Operation Success/Failure Response
- Request samples Content type application/json
Copy
Expand all Collapse all Response samples Content type application/json
Example Operation succeeded.
Operation failed.
Copy
Expand all Collapse all Set the latency level for the stream. post /api/admin/config/video/streamlatencylevel
/api/admin/config/video/streamlatencylevel
Sets the latency level that determines how much video is buffered between the server and viewer. Less latency can end up with more buffering.
-
Request Body schema: application/json
Responses 200 Operation Success/Failure Response
- Request samples Content type application/json
Copy
Expand all Collapse all Response samples Content type application/json
Example Operation succeeded.
Operation failed.
Copy
Expand all Collapse all Set the configuration of your stream output. post /api/admin/config/video/streamoutputvariants
/api/admin/config/video/streamoutputvariants
Sets the detailed configuration for all of the stream variants you support.
-
Request Body schema: application/json
value string or integer or object or boolean
Responses 200 Operation Success/Failure Response
- Request samples Content type application/json
Copy
Expand all Collapse all Response samples Content type application/json
Example Operation succeeded.
Operation failed.
Copy
Expand all Collapse all Set the video codec. post /api/admin/config/video/codec
/api/admin/config/video/codec
Sets the specific video codec that will be used for video encoding. Some codecs will support hardware acceleration. Not all codecs will be supported for all systems.
-
Request Body schema: application/json
value string
The video codec to change to.
-
Responses 200 Operation Success/Failure Response
- Request samples Content type application/json
Copy
Expand all Collapse all Response samples Content type application/json
Example Operation succeeded.
Operation failed.
Copy
Expand all Collapse all Set your storage configration. Sets your S3 storage provider configuration details to enable external storage.
-
Request Body schema: application/json
value string or integer or object or boolean
Responses 200 Operation Success/Failure Response
- Request samples Content type application/json
Copy
Expand all Collapse all { "value" :
{ "enabled" : true ,
"accessKey" : "e1ac500y7000500047156bd060" ,
"secret" : "H8FH8eSxM2K/S42CUg5K000Tt4WY2fI" ,
"bucket" : "video" ,
"region" : "us-west-000"
} } Response samples Content type application/json
Example Operation succeeded.
Operation failed.
Copy
Expand all Collapse all Set your social handles. post /api/admin/config/socialhandles
/api/admin/config/socialhandles
Sets the external links to social networks and profiles.
-
Request Body schema: application/json
value string or integer or object or boolean
Responses 200 Operation Success/Failure Response
- Request samples Content type application/json
Copy
Expand all Collapse all Response samples Content type application/json
Example Operation succeeded.
Operation failed.
Copy
Expand all Collapse all Custom CSS styles to be used in the web front endpoints. post /api/admin/config/customstyles
/api/admin/config/customstyles
Save a string containing CSS to be inserted in to the web frontend page.
-
Request Body schema: application/json
value string or integer or object or boolean
Responses 200 Operation Success/Failure Response
- Request samples Content type application/json
Copy
Expand all Collapse all Response samples Content type application/json
Example Operation succeeded.
Operation failed.
Copy
Expand all Collapse all Viewers Over Time get /api/admin/viewersOverTime
/api/admin/viewersOverTime
Get the tracked viewer count over the collected period.
-
Response samples Content type application/json
Copy
Expand all Collapse all Hardware Stats get /api/admin/hardwarestats Get the CPU, Memory and Disk utilization levels over the collected period.
-
Response samples Content type application/json
Copy
Expand all Collapse all Return all webhooks. Return all of the configured webhooks for external events.
-
Responses 200 Webhooks are returned
- Response samples Content type application/json
Copy
Expand all Collapse all Set external action URLs. post /api/admin/config/externalactions
/api/admin/config/externalactions
Set a collection of external action URLs that are displayed in the UI.
-
Request Body schema: application/json
Array ()
url string
URL of the external action content.
-
title string
The title to put on the external action button.
-
description string
Optional additional description to display in the UI.
-
icon string
The URL to an image to place on the external action button.
-
color string
Optional color to use for drawing the action button.
-
openExternally boolean
If set this action will open in a new browser tab instead of an internal modal.
-
Responses 200 Actions have been updated.
- Request samples Content type application/json
Copy
Expand all Collapse all [ { "url" : "string" ,
"title" : "string" ,
"description" : "string" ,
"icon" : "string" ,
"color" : "string" ,
"openExternally" : true
} ] Delete a single webhook. post /api/admin/webhooks/delete
/api/admin/webhooks/delete
Delete a single webhook by its ID.
-
Request Body schema: application/json
id string
The webhook id to delete
-
Request samples Content type application/json
Copy
Expand all Collapse all Create a webhook. post /api/admin/webhooks/create
/api/admin/webhooks/create
Create a single webhook that acts on the requested events.
-
Request Body schema: application/json
url string
The url to post the events to.
-
events Array of strings
The events to be notified about.
-
Request samples Content type application/json
Copy
Expand all Collapse all Response samples Content type application/json
Copy
Expand all Collapse all Endpoints related to the chat interface.
-
Historical Chat Messages Used to get all chat messages prior to connecting to the websocket.
-
Response samples Content type application/json
Copy
Expand all Collapse all Get Custom Emoji Get a list of custom emoji that are supported in chat.
-
Response samples Content type application/json
Copy
Expand all Collapse all APIs built to allow 3rd parties to interact with an Owncast server.
-
Set the stream title. post /api/integrations/streamtitle
/api/integrations/streamtitle
Set the title of the currently streaming content.
-
Request Body schema: application/json
value string or integer or object or boolean
Responses 200 Operation Success/Failure Response
- Request samples Content type application/json
Copy
Expand all Collapse all Response samples Content type application/json
Example Operation succeeded.
Operation failed.
Copy
Expand all Collapse all Send a user chat message. post /api/integrations/chat/user
/api/integrations/chat/user
Send a chat message on behalf of a user. Could be a bot name or a real user.
-
Request Body schema: application/json
user string
The user you want to send this message as.
-
body string
The message text that will be sent as the user.
-
Request samples Content type application/json
Copy
Expand all Collapse all { "user" : "string" ,
"body" : "string"
} Response samples Content type application/json
Copy
Expand all Collapse all { "success" : true ,
"message" : "sent"
} Send a system chat message. post /api/integrations/chat/system
/api/integrations/chat/system
Send a chat message on behalf of the system/server.
-
Request Body schema: application/json
body string
The message text that will be sent as the system user.
-
Request samples Content type application/json
Copy
Expand all Collapse all Response samples Content type application/json
Copy
Expand all Collapse all { "success" : true ,
"message" : "sent"
} Send a chat action. post /api/integrations/chat/action
/api/integrations/chat/action
Send an action that took place to the chat.
-
Request Body schema: application/json
body required
string
The message text that will be sent as the system user.
-
author string
An optional user name that performed the action.
-
Request samples Content type application/json
Copy
Expand all Collapse all Response samples Content type application/json
Copy
Expand all Collapse all { "success" : true ,
"message" : "sent"
} Create an access token. post /api/admin/accesstokens/create
/api/admin/accesstokens/create
Create a single access token that has access to the access scopes provided.
-
Request Body schema: application/json
name string
The human-readable name to give this access token.
-
scopes
Request samples Content type application/json
Copy
Expand all Collapse all Response samples Content type application/json
Copy
Expand all Collapse all Delete an access token. post /api/admin/accesstokens/delete
/api/admin/accesstokens/delete
Delete a single access token.
-
Request Body schema: application/json
Request samples Content type application/json
Copy
Expand all Collapse all Response samples Content type application/json
Copy
Expand all Collapse all Return all access tokens. get /api/admin/accesstokens Return all of the available access tokens.
-
Responses 200 Tokens are returned
- Response samples Content type application/json
Copy
Expand all Collapse all Set external action URLs. post /api/admin/config/externalactions
/api/admin/config/externalactions
Set a collection of external action URLs that are displayed in the UI.
-
Request Body schema: application/json
Array ()
url string
URL of the external action content.
-
title string
The title to put on the external action button.
-
description string
Optional additional description to display in the UI.
-
icon string
The URL to an image to place on the external action button.
-
color string
Optional color to use for drawing the action button.
-
openExternally boolean
If set this action will open in a new browser tab instead of an internal modal.
-
Responses 200 Actions have been updated.
- Request samples Content type application/json
Copy
Expand all Collapse all [ { "url" : "string" ,
"title" : "string" ,
"description" : "string" ,
"icon" : "string" ,
"color" : "string" ,
"openExternally" : true
} ] Return a list of currently connected clients get /api/integrations/clients
/api/integrations/clients
Return a list of currently connected clients with optional geo details.
-
Responses 200 Successful response of an array of clients
- Response samples Content type application/json
Copy
Expand all Collapse all [ { "connectedAt" : "2020-10-06T23:20:44.588649-07:00" ,
"messageCount" : 3 ,
"userAgent" : "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.89 Safari/537.36" ,
"ipAddress" : "172.217.164.110" ,
"username" : "coolperson42" ,
"clientID" : "2ba20dd34f911c198df3218ddc64c740" ,
} ] Historical Chat Messages get /api/integrations/chat Used to get the backlog of chat messages.
-
Response samples Content type application/json
Copy
Expand all Collapse all Update the visibility of chat messages. post /api/integrations/chat/updatemessagevisibility
/api/integrations/chat/updatemessagevisibility
Pass an array of IDs you want to change the chat visibility of.
-
Request Body schema: application/json
visible boolean
Are these messages visible.
-
idArray
Responses 200 Operation Success/Failure Response
- Request samples Content type application/json
Copy
Expand all Collapse all Response samples Content type application/json
Example Operation succeeded.
Operation failed.
Copy
Expand all Collapse all Information The client configuration. Information useful for the user interface.
-
Response samples Content type application/json
Copy
Expand all Collapse all { "name" : "string" ,
"summary" : "string" ,
"logo" : "string" ,
"extraPageContent" : "<p>This page is <strong>super</strong> cool!" ,
"version" : "Owncast v0.0.3-macOS (ef3796a033b32a312ebf5b334851cbf9959e7ecb)"
} Current Status This endpoint is used to discover when a server is broadcasting, the number of active viewers as well as other useful information for updating the user interface.
-
Response samples Content type application/json
Copy
Expand all Collapse all { "lastConnectTime" : "2020-10-03T21:36:22-05:00" ,
"lastDisconnectTime" : null ,
"online" : true ,
"overallMaxViewerCount" : 420 ,
"sessionMaxViewerCount" : 12 ,
"viewerCount" : 7
} Yellow Pages Information Information to be used in the Yellow Pages service, a global directory of Owncast servers.
-
Response samples Content type application/json
Copy
Expand all Collapse all { "name" : "string" ,
"description" : "string" ,
"logo" : "string" ,
"nsfw" : true ,
"online" : true ,
"viewerCount" : 0 ,
"overallMaxViewerCount" : 0 ,
"sessionMaxViewerCount" : 0 ,
"lastConnectTime" : "2019-08-24T14:15:22Z"
}
+ " fill="currentColor">
Owncast (0.0.8-develop) Download OpenAPI specification:Download
Owncast is a self-hosted live video and web chat server for use with existing popular broadcasting software. The following APIs represent the state in the development branch.
+
AdminBasicAuthThe username for admin basic auth is admin and the password is the stream key.
+
Security Scheme Type HTTP HTTP Authorization Scheme basic
AccessToken3rd party integration auth where a service user must provide an access token.
+
Security Scheme Type HTTP HTTP Authorization Scheme bearer
UserToken3rd party integration auth where a service user must provide an access token.
+
Security Scheme Type API Key Query parameter name: accessToken
Admin operations requiring authentication.
+
Server status and broadcaster Responses 200 Server status and broadcaster details
+ Response samples Content type application/json
Copy
Expand all Collapse all { "broadcaster" :
{ "remoteAddr" : "172.217.164.110" ,
"time" : "2020-10-06T23:20:44.588649-07:00" ,
} , "online" : true ,
"viewerCount" : 3 ,
"overallPeakViewerCount" : 4 ,
"sessionPeakViewerCount" : 4 ,
"versionNumber" : "0.0.3"
} Disconnect Broadcaster post /api/admin/disconnect Disconnect the active inbound stream, if one exists, and terminate the broadcast.
+
Responses 200 Operation Success/Failure Response
+ Response samples Content type application/json
Example Operation succeeded.
Operation failed.
Copy
Expand all Collapse all Reset your YP registration key. Used when there is a problem with your registration to the Owncast Directory via the YP APIs. This will reset your local registration key.
+
Responses 200 Operation Success/Failure Response
+ Response samples Content type application/json
Example Operation succeeded.
Operation failed.
Copy
Expand all Collapse all Return a list of currently connected clients Return a list of currently connected clients with optional geo details.
+
Responses 200 Successful response of an array of clients
+ Response samples Content type application/json
Copy
Expand all Collapse all [ { "connectedAt" : "2020-10-06T23:20:44.588649-07:00" ,
"messageCount" : 3 ,
"userAgent" : "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.89 Safari/537.36" ,
"ipAddress" : "172.217.164.110" ,
"user" :
{ "id" : "yklw5Imng" ,
"displayName" : "awesome-pizza" ,
"displayColor" : 42 ,
"createdAt" : "2021-07-08T20:21:25.303402404-07:00" ,
"previousNames" : "awesome-pizza,coolPerson23"
} } ] Return a list of currently connected clients get /api/admin/users/disabled
/api/admin/users/disabled
Return a list of currently connected clients with optional geo details.
+
Responses 200 A collection of users.
+ Response samples Content type application/json
Copy
Expand all Collapse all [ { "id" : "yklw5Imng" ,
"displayName" : "awesome-pizza" ,
"displayColor" : 42 ,
"createdAt" : "2019-08-24T14:15:22Z" ,
"previousNames" : "awesome-pizza,user42"
} ] Return recent log entries Responses 200 Response of server log entries
+ Response samples Content type application/json
Copy
Expand all Collapse all [ { "message" : "RTMP server is listening for incoming stream on port: 1935" ,
"level" : "info" ,
"time" : "2020-10-29T18:35:35.011823-07:00"
} ] Return recent warning and error logs. get /api/admin/logs/warnings Return recent warning and error logs.
+
Responses 200 Response of server log entries
+ Response samples Content type application/json
Copy
Expand all Collapse all [ { "message" : "RTMP server is listening for incoming stream on port: 1935" ,
"level" : "info" ,
"time" : "2020-10-29T18:35:35.011823-07:00"
} ] Server Configuration get /api/admin/serverconfig Get the current configuration of the Owncast server.
+
Response samples Content type application/json
Copy
Expand all Collapse all { "instanceDetails" :
{ "name" : "string" ,
"summary" : "string" ,
"logo" : "string" ,
"extraPageContent" : "<p>This page is <strong>super</strong> cool!" ,
"version" : "Owncast v0.0.3-macOS (ef3796a033b32a312ebf5b334851cbf9959e7ecb)"
} , "ffmpegPath" : "string" ,
"webServerPort" : 0 ,
"rtmpServerPort" : 0 ,
"videoSettings" :
{ "videoQualityVariants" :
[ { "videoPassthrough" : true ,
"audioPassthrough" : true ,
"videoBitrate" : 0 ,
"audioBitrate" : 0 ,
"scaledWidth" : 0 ,
"scaledHeight" : 0 ,
"framerate" : 0 ,
"cpuUsageLevel" : 0
} ] , "latencyLevel" : 0
} , "yp" :
{ "enabled" : false ,
"instanceUrl" : "string"
} } Chat messages, unfiltered. get /api/admin/chat/messages Get a list of all chat messages with no filters applied.
+
Response samples Content type application/json
Copy
Expand all Collapse all Update the visibility of chat messages. post /api/admin/chat/updatemessagevisibility
/api/admin/chat/updatemessagevisibility
Pass an array of IDs you want to change the chat visibility of.
+
Request Body schema: application/json
visible boolean
Are these messages visible.
+
idArray
Responses 200 Operation Success/Failure Response
+ Request samples Content type application/json
Copy
Expand all Collapse all Response samples Content type application/json
Example Operation succeeded.
Operation failed.
Copy
Expand all Collapse all Enable or disable a single user. post /api/admin/chat/users/setenabled
/api/admin/chat/users/setenabled
Enable or disable a single user. Disabling will also hide all the user's chat messages.
+
Request Body schema: application/json
userId enabled boolean
Set the enabled state of this user.
+
Responses 200 Operation Success/Failure Response
+ Request samples Content type application/json
Copy
Expand all Collapse all { "userId" : "yklw5Imng" ,
"enabled" : true
} Response samples Content type application/json
Example Operation succeeded.
Operation failed.
Copy
Expand all Collapse all Set the stream key. post /api/admin/config/key Set the stream key. Also used as the admin password.
+
Request Body schema: application/json
value string or integer or object or boolean
Responses 200 Operation Success/Failure Response
+ Request samples Content type application/json
Copy
Expand all Collapse all Response samples Content type application/json
Example Operation succeeded.
Operation failed.
Copy
Expand all Collapse all Set the custom page content. post /api/admin/config/pagecontent
/api/admin/config/pagecontent
Set the custom page content using markdown.
+
Request Body schema: application/json
value string or integer or object or boolean
Responses 200 Operation Success/Failure Response
+ Request samples Content type application/json
Copy
Expand all Collapse all "# Welcome to my cool server!<br><br>I _hope_ you enjoy it."
Response samples Content type application/json
Example Operation succeeded.
Operation failed.
Copy
Expand all Collapse all Set the stream title. post /api/admin/config/streamtitle
/api/admin/config/streamtitle
Set the title of the currently streaming content.
+
Request Body schema: application/json
value string or integer or object or boolean
Responses 200 Operation Success/Failure Response
+ Request samples Content type application/json
Copy
Expand all Collapse all Response samples Content type application/json
Example Operation succeeded.
Operation failed.
Copy
Expand all Collapse all Set the server name. post /api/admin/config/name Set the name associated with your server. Often is your name, username or identity.
+
Request Body schema: application/json
value string or integer or object or boolean
Responses 200 Operation Success/Failure Response
+ Request samples Content type application/json
Copy
Expand all Collapse all Response samples Content type application/json
Example Operation succeeded.
Operation failed.
Copy
Expand all Collapse all Set the server summary. post /api/admin/config/serversummary
/api/admin/config/serversummary
Set the summary of your server's streaming content.
+
Request Body schema: application/json
value string or integer or object or boolean
Responses 200 Operation Success/Failure Response
+ Request samples Content type application/json
Copy
Expand all Collapse all Response samples Content type application/json
Example Operation succeeded.
Operation failed.
Copy
Expand all Collapse all Set the server logo. post /api/admin/config/logo Set the logo for your server. Path is relative to webroot.
+
Request Body schema: application/json
value string or integer or object or boolean
Responses 200 Operation Success/Failure Response
+ Request samples Content type application/json
Copy
Expand all Collapse all Response samples Content type application/json
Example Operation succeeded.
Operation failed.
Copy
Expand all Collapse all Set the server tags. post /api/admin/config/tags Set the tags displayed for your server and the categories you can show up in on the directory.
+
Request Body schema: application/json
value string or integer or object or boolean
Responses 200 Operation Success/Failure Response
+ Request samples Content type application/json
Copy
Expand all Collapse all { "value" :
[ "games" ,
"music" ,
"streaming"
] } Response samples Content type application/json
Example Operation succeeded.
Operation failed.
Copy
Expand all Collapse all Set the ffmpeg binary path post /api/admin/config/ffmpegpath
/api/admin/config/ffmpegpath
Set the path for a specific copy of ffmpeg on your system.
+
Request Body schema: application/json
value string or integer or object or boolean
Responses 200 Operation Success/Failure Response
+ Request samples Content type application/json
Copy
Expand all Collapse all Response samples Content type application/json
Example Operation succeeded.
Operation failed.
Copy
Expand all Collapse all Set the owncast web port. post /api/admin/config/webserverport
/api/admin/config/webserverport
Set the port the owncast web server should listen on.
+
Request Body schema: application/json
value string or integer or object or boolean
Responses 200 Operation Success/Failure Response
+ Request samples Content type application/json
Copy
Expand all Collapse all Response samples Content type application/json
Example Operation succeeded.
Operation failed.
Copy
Expand all Collapse all Set the inbound rtmp server port. post /api/admin/config/rtmpserverport
/api/admin/config/rtmpserverport
Set the port where owncast service will listen for inbound broadcasts.
+
Request Body schema: application/json
value string or integer or object or boolean
Responses 200 Operation Success/Failure Response
+ Request samples Content type application/json
Copy
Expand all Collapse all Response samples Content type application/json
Example Operation succeeded.
Operation failed.
Copy
Expand all Collapse all Mark if your stream is not safe for work post /api/admin/config/nsfw Mark if your stream can be consitered not safe for work. Used in different contexts, including the directory for filtering purposes.
+
Request Body schema: application/json
value string or integer or object or boolean
Responses 200 Operation Success/Failure Response
+ Request samples Content type application/json
Copy
Expand all Collapse all Response samples Content type application/json
Example Operation succeeded.
Operation failed.
Copy
Expand all Collapse all Set if this server supports the Owncast directory. post /api/admin/config/directoryenabled
/api/admin/config/directoryenabled
If set to true the server will attempt to register itself with the Owncast Directory . Off by default.
+
Request Body schema: application/json
value string or integer or object or boolean
Responses 200 Operation Success/Failure Response
+ Request samples Content type application/json
Copy
Expand all Collapse all Response samples Content type application/json
Example Operation succeeded.
Operation failed.
Copy
Expand all Collapse all Set the public url of this owncast server. post /api/admin/config/serverurl
/api/admin/config/serverurl
Set the public url of this owncast server. Used for the directory and optional integrations.
+
Request Body schema: application/json
value string or integer or object or boolean
Responses 200 Operation Success/Failure Response
+ Request samples Content type application/json
Copy
Expand all Collapse all Response samples Content type application/json
Example Operation succeeded.
Operation failed.
Copy
Expand all Collapse all Set the latency level for the stream. post /api/admin/config/video/streamlatencylevel
/api/admin/config/video/streamlatencylevel
Sets the latency level that determines how much video is buffered between the server and viewer. Less latency can end up with more buffering.
+
Request Body schema: application/json
Responses 200 Operation Success/Failure Response
+ Request samples Content type application/json
Copy
Expand all Collapse all Response samples Content type application/json
Example Operation succeeded.
Operation failed.
Copy
Expand all Collapse all Set the configuration of your stream output. post /api/admin/config/video/streamoutputvariants
/api/admin/config/video/streamoutputvariants
Sets the detailed configuration for all of the stream variants you support.
+
Request Body schema: application/json
value string or integer or object or boolean
Responses 200 Operation Success/Failure Response
+ Request samples Content type application/json
Copy
Expand all Collapse all Response samples Content type application/json
Example Operation succeeded.
Operation failed.
Copy
Expand all Collapse all Set the video codec. post /api/admin/config/video/codec
/api/admin/config/video/codec
Sets the specific video codec that will be used for video encoding. Some codecs will support hardware acceleration. Not all codecs will be supported for all systems.
+
Request Body schema: application/json
value string
The video codec to change to.
+
Responses 200 Operation Success/Failure Response
+ Request samples Content type application/json
Copy
Expand all Collapse all Response samples Content type application/json
Example Operation succeeded.
Operation failed.
Copy
Expand all Collapse all Set your storage configration. Sets your S3 storage provider configuration details to enable external storage.
+
Request Body schema: application/json
value string or integer or object or boolean
Responses 200 Operation Success/Failure Response
+ Request samples Content type application/json
Copy
Expand all Collapse all { "value" :
{ "enabled" : true ,
"accessKey" : "e1ac500y7000500047156bd060" ,
"secret" : "H8FH8eSxM2K/S42CUg5K000Tt4WY2fI" ,
"bucket" : "video" ,
"region" : "us-west-000"
} } Response samples Content type application/json
Example Operation succeeded.
Operation failed.
Copy
Expand all Collapse all Set your social handles. post /api/admin/config/socialhandles
/api/admin/config/socialhandles
Sets the external links to social networks and profiles.
+
Request Body schema: application/json
value string or integer or object or boolean
Responses 200 Operation Success/Failure Response
+ Request samples Content type application/json
Copy
Expand all Collapse all Response samples Content type application/json
Example Operation succeeded.
Operation failed.
Copy
Expand all Collapse all Custom CSS styles to be used in the web front endpoints. post /api/admin/config/customstyles
/api/admin/config/customstyles
Save a string containing CSS to be inserted in to the web frontend page.
+
Request Body schema: application/json
value string or integer or object or boolean
Responses 200 Operation Success/Failure Response
+ Request samples Content type application/json
Copy
Expand all Collapse all Response samples Content type application/json
Example Operation succeeded.
Operation failed.
Copy
Expand all Collapse all Viewers Over Time get /api/admin/viewersOverTime
/api/admin/viewersOverTime
Get the tracked viewer count over the collected period.
+
Response samples Content type application/json
Copy
Expand all Collapse all Hardware Stats get /api/admin/hardwarestats Get the CPU, Memory and Disk utilization levels over the collected period.
+
Response samples Content type application/json
Copy
Expand all Collapse all Return all webhooks. Return all of the configured webhooks for external events.
+
Responses 200 Webhooks are returned
+ Response samples Content type application/json
Copy
Expand all Collapse all Set external action URLs. post /api/admin/config/externalactions
/api/admin/config/externalactions
Set a collection of external action URLs that are displayed in the UI.
+
Request Body schema: application/json
Array ()
url string
URL of the external action content.
+
title string
The title to put on the external action button.
+
description string
Optional additional description to display in the UI.
+
icon string
The URL to an image to place on the external action button.
+
color string
Optional color to use for drawing the action button.
+
openExternally boolean
If set this action will open in a new browser tab instead of an internal modal.
+
Responses 200 Actions have been updated.
+ Request samples Content type application/json
Copy
Expand all Collapse all [ { "url" : "string" ,
"title" : "string" ,
"description" : "string" ,
"icon" : "string" ,
"color" : "string" ,
"openExternally" : true
} ] Delete a single webhook. post /api/admin/webhooks/delete
/api/admin/webhooks/delete
Delete a single webhook by its ID.
+
Request Body schema: application/json
id string
The webhook id to delete
+
Request samples Content type application/json
Copy
Expand all Collapse all Create a webhook. post /api/admin/webhooks/create
/api/admin/webhooks/create
Create a single webhook that acts on the requested events.
+
Request Body schema: application/json
url string
The url to post the events to.
+
events Array of strings
The events to be notified about.
+
Request samples Content type application/json
Copy
Expand all Collapse all Response samples Content type application/json
Copy
Expand all Collapse all Endpoints related to the chat interface.
+
Register a chat user Register a user that returns an access token for accessing chat.
+
Request Body schema: application/json
displayName string
Optionally provide a display name you want to assign to this user when registering.
+
Request samples Content type application/json
Copy
Expand all Collapse all Response samples Content type application/json
Copy
Expand all Collapse all { "id" : "string" ,
"accessToken" : "string" ,
"displayName" : "string"
} Chat Messages Backlog Used to get chat messages prior to connecting to the websocket.
+
Response samples Content type application/json
Copy
Expand all Collapse all Get Custom Emoji Get a list of custom emoji that are supported in chat.
+
Response samples Content type application/json
Copy
Expand all Collapse all APIs built to allow 3rd parties to interact with an Owncast server.
+
Set the stream title. post /api/integrations/streamtitle
/api/integrations/streamtitle
Set the title of the currently streaming content.
+
Request Body schema: application/json
value string or integer or object or boolean
Responses 200 Operation Success/Failure Response
+ Request samples Content type application/json
Copy
Expand all Collapse all Response samples Content type application/json
Example Operation succeeded.
Operation failed.
Copy
Expand all Collapse all Send a chat message. post /api/integrations/chat/send
/api/integrations/chat/send
Send a chat message on behalf of a 3rd party integration, bot or service.
+
Request Body schema: application/json
body string
The message text that will be sent as the user.
+
Request samples Content type application/json
Copy
Expand all Collapse all Response samples Content type application/json
Copy
Expand all Collapse all { "success" : true ,
"message" : "sent"
} Send a system chat message. post /api/integrations/chat/system
/api/integrations/chat/system
Send a chat message on behalf of the system/server.
+
Request Body schema: application/json
body string
The message text that will be sent as the system user.
+
Request samples Content type application/json
Copy
Expand all Collapse all Response samples Content type application/json
Copy
Expand all Collapse all { "success" : true ,
"message" : "sent"
} Send a chat action. post /api/integrations/chat/action
/api/integrations/chat/action
Send an action that took place to the chat.
+
Request Body schema: application/json
body required
string
The message text that will be sent as the system user.
+
author string
An optional user name that performed the action.
+
Request samples Content type application/json
Copy
Expand all Collapse all Response samples Content type application/json
Copy
Expand all Collapse all { "success" : true ,
"message" : "sent"
} Create an access token. post /api/admin/accesstokens/create
/api/admin/accesstokens/create
Create a single access token that has access to the access scopes provided.
+
Request Body schema: application/json
name string
The human-readable name to give this access token.
+
scopes
Request samples Content type application/json
Copy
Expand all Collapse all Response samples Content type application/json
Copy
Expand all Collapse all Delete an access token. post /api/admin/accesstokens/delete
/api/admin/accesstokens/delete
Delete a single access token.
+
Request Body schema: application/json
Request samples Content type application/json
Copy
Expand all Collapse all Response samples Content type application/json
Copy
Expand all Collapse all Return all access tokens. get /api/admin/accesstokens Return all of the available access tokens.
+
Responses 200 Tokens are returned
+ Response samples Content type application/json
Copy
Expand all Collapse all Set external action URLs. post /api/admin/config/externalactions
/api/admin/config/externalactions
Set a collection of external action URLs that are displayed in the UI.
+
Request Body schema: application/json
Array ()
url string
URL of the external action content.
+
title string
The title to put on the external action button.
+
description string
Optional additional description to display in the UI.
+
icon string
The URL to an image to place on the external action button.
+
color string
Optional color to use for drawing the action button.
+
openExternally boolean
If set this action will open in a new browser tab instead of an internal modal.
+
Responses 200 Actions have been updated.
+ Request samples Content type application/json
Copy
Expand all Collapse all [ { "url" : "string" ,
"title" : "string" ,
"description" : "string" ,
"icon" : "string" ,
"color" : "string" ,
"openExternally" : true
} ] Return a list of currently connected clients get /api/integrations/clients
/api/integrations/clients
Return a list of currently connected clients with optional geo details.
+
Responses 200 Successful response of an array of clients
+ Response samples Content type application/json
Copy
Expand all Collapse all [ { "connectedAt" : "2020-10-06T23:20:44.588649-07:00" ,
"messageCount" : 3 ,
"userAgent" : "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.89 Safari/537.36" ,
"ipAddress" : "172.217.164.110" ,
"user" :
{ "id" : "yklw5Imng" ,
"displayName" : "awesome-pizza" ,
"displayColor" : 42 ,
"createdAt" : "2021-07-08T20:21:25.303402404-07:00" ,
"previousNames" : "awesome-pizza,coolPerson23"
} } ] Historical Chat Messages get /api/integrations/chat Used to get the backlog of chat messages.
+
Response samples Content type application/json
Copy
Expand all Collapse all Update the visibility of chat messages. post /api/integrations/chat/updatemessagevisibility
/api/integrations/chat/updatemessagevisibility
Pass an array of IDs you want to change the chat visibility of.
+
Request Body schema: application/json
visible boolean
Are these messages visible.
+
idArray
Responses 200 Operation Success/Failure Response
+ Request samples Content type application/json
Copy
Expand all Collapse all Response samples Content type application/json
Example Operation succeeded.
Operation failed.
Copy
Expand all Collapse all Information The client configuration. Information useful for the user interface.
+
Response samples Content type application/json
Copy
Expand all Collapse all { "name" : "string" ,
"summary" : "string" ,
"logo" : "string" ,
"extraPageContent" : "<p>This page is <strong>super</strong> cool!" ,
"version" : "Owncast v0.0.3-macOS (ef3796a033b32a312ebf5b334851cbf9959e7ecb)"
} Mark the current viewer as active. For tracking viewer count, periodically hit the ping endpoint.
+
Current Status This endpoint is used to discover when a server is broadcasting, the number of active viewers as well as other useful information for updating the user interface.
+
Response samples Content type application/json
Copy
Expand all Collapse all { "lastConnectTime" : "2020-10-03T21:36:22-05:00" ,
"lastDisconnectTime" : null ,
"online" : true ,
"overallMaxViewerCount" : 420 ,
"sessionMaxViewerCount" : 12 ,
"viewerCount" : 7
} Yellow Pages Information Information to be used in the Yellow Pages service, a global directory of Owncast servers.
+
Response samples Content type application/json
Copy
Expand all Collapse all { "name" : "string" ,
"description" : "string" ,
"logo" : "string" ,
"nsfw" : true ,
"online" : true ,
"viewerCount" : 0 ,
"overallMaxViewerCount" : 0 ,
"sessionMaxViewerCount" : 0 ,
"lastConnectTime" : "2019-08-24T14:15:22Z"
}
|'
Don't load/preload app-standalone-chat.js or app-video-only.js.
@@ -120,4 +121,4 @@
-