Compare commits
4 Commits
develop
...
gek/router
Author | SHA1 | Date |
---|---|---|
|
e1a3ecc38b | 2 years ago |
|
83e891022e | 2 years ago |
|
e26f836159 | 2 years ago |
|
e62aa8896c | 2 years ago |
70 changed files with 2063 additions and 1999 deletions
@ -1,13 +0,0 @@
@@ -1,13 +0,0 @@
|
||||
package controllers |
||||
|
||||
import ( |
||||
"net/http" |
||||
|
||||
"github.com/owncast/owncast/core/rtmp" |
||||
) |
||||
|
||||
// DisconnectInboundConnection will force-disconnect an inbound stream.
|
||||
func DisconnectInboundConnection(w http.ResponseWriter, r *http.Request) { |
||||
rtmp.Disconnect() |
||||
w.WriteHeader(http.StatusOK) |
||||
} |
@ -1,35 +0,0 @@
@@ -1,35 +0,0 @@
|
||||
package admin |
||||
|
||||
import ( |
||||
"encoding/json" |
||||
"net/http" |
||||
|
||||
"github.com/owncast/owncast/controllers" |
||||
"github.com/owncast/owncast/core/data" |
||||
) |
||||
|
||||
// SetCustomColorVariableValues sets the custom color variables.
|
||||
func SetCustomColorVariableValues(w http.ResponseWriter, r *http.Request) { |
||||
if !requirePOST(w, r) { |
||||
return |
||||
} |
||||
|
||||
type request struct { |
||||
Value map[string]string `json:"value"` |
||||
} |
||||
|
||||
decoder := json.NewDecoder(r.Body) |
||||
var values request |
||||
|
||||
if err := decoder.Decode(&values); err != nil { |
||||
controllers.WriteSimpleResponse(w, false, "unable to update appearance variable values") |
||||
return |
||||
} |
||||
|
||||
if err := data.SetCustomColorVariableValues(values.Value); err != nil { |
||||
controllers.WriteSimpleResponse(w, false, err.Error()) |
||||
return |
||||
} |
||||
|
||||
controllers.WriteSimpleResponse(w, true, "custom appearance variables updated") |
||||
} |
@ -1,880 +0,0 @@
@@ -1,880 +0,0 @@
|
||||
package admin |
||||
|
||||
import ( |
||||
"encoding/json" |
||||
"fmt" |
||||
"net" |
||||
"net/http" |
||||
"net/netip" |
||||
"os" |
||||
"path/filepath" |
||||
"reflect" |
||||
"strings" |
||||
|
||||
"github.com/owncast/owncast/activitypub/outbox" |
||||
"github.com/owncast/owncast/controllers" |
||||
"github.com/owncast/owncast/core/chat" |
||||
"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" |
||||
log "github.com/sirupsen/logrus" |
||||
"github.com/teris-io/shortid" |
||||
) |
||||
|
||||
// ConfigValue is a container object that holds a value, is encoded, and saved to the database.
|
||||
type ConfigValue struct { |
||||
Value interface{} `json:"value"` |
||||
} |
||||
|
||||
// SetTags will handle the web config request to set tags.
|
||||
func SetTags(w http.ResponseWriter, r *http.Request) { |
||||
if !requirePOST(w, r) { |
||||
return |
||||
} |
||||
|
||||
configValues, success := getValuesFromRequest(w, r) |
||||
if !success { |
||||
return |
||||
} |
||||
|
||||
tagStrings := make([]string, 0) |
||||
for _, tag := range configValues { |
||||
tagStrings = append(tagStrings, strings.TrimLeft(tag.Value.(string), "#")) |
||||
} |
||||
|
||||
if err := data.SetServerMetadataTags(tagStrings); err != nil { |
||||
controllers.WriteSimpleResponse(w, false, err.Error()) |
||||
return |
||||
} |
||||
|
||||
// Update Fediverse followers about this change.
|
||||
if err := outbox.UpdateFollowersWithAccountUpdates(); err != nil { |
||||
controllers.WriteSimpleResponse(w, false, err.Error()) |
||||
return |
||||
} |
||||
|
||||
controllers.WriteSimpleResponse(w, true, "changed") |
||||
} |
||||
|
||||
// SetStreamTitle will handle the web config request to set the current stream title.
|
||||
func SetStreamTitle(w http.ResponseWriter, r *http.Request) { |
||||
if !requirePOST(w, r) { |
||||
return |
||||
} |
||||
|
||||
configValue, success := getValueFromRequest(w, r) |
||||
if !success { |
||||
return |
||||
} |
||||
|
||||
value := configValue.Value.(string) |
||||
|
||||
if err := data.SetStreamTitle(value); err != nil { |
||||
controllers.WriteSimpleResponse(w, false, err.Error()) |
||||
return |
||||
} |
||||
if value != "" { |
||||
sendSystemChatAction(fmt.Sprintf("Stream title changed to **%s**", value), true) |
||||
go webhooks.SendStreamStatusEvent(models.StreamTitleUpdated) |
||||
} |
||||
controllers.WriteSimpleResponse(w, true, "changed") |
||||
} |
||||
|
||||
// ExternalSetStreamTitle will change the stream title on behalf of an external integration API request.
|
||||
func ExternalSetStreamTitle(integration user.ExternalAPIUser, w http.ResponseWriter, r *http.Request) { |
||||
SetStreamTitle(w, r) |
||||
} |
||||
|
||||
func sendSystemChatAction(messageText string, ephemeral bool) { |
||||
if err := chat.SendSystemAction(messageText, ephemeral); err != nil { |
||||
log.Errorln(err) |
||||
} |
||||
} |
||||
|
||||
// SetServerName will handle the web config request to set the server's name.
|
||||
func SetServerName(w http.ResponseWriter, r *http.Request) { |
||||
if !requirePOST(w, r) { |
||||
return |
||||
} |
||||
|
||||
configValue, success := getValueFromRequest(w, r) |
||||
if !success { |
||||
return |
||||
} |
||||
|
||||
if err := data.SetServerName(configValue.Value.(string)); err != nil { |
||||
controllers.WriteSimpleResponse(w, false, err.Error()) |
||||
return |
||||
} |
||||
|
||||
// Update Fediverse followers about this change.
|
||||
if err := outbox.UpdateFollowersWithAccountUpdates(); err != nil { |
||||
controllers.WriteSimpleResponse(w, false, err.Error()) |
||||
return |
||||
} |
||||
|
||||
controllers.WriteSimpleResponse(w, true, "changed") |
||||
} |
||||
|
||||
// SetServerSummary will handle the web config request to set the about/summary text.
|
||||
func SetServerSummary(w http.ResponseWriter, r *http.Request) { |
||||
if !requirePOST(w, r) { |
||||
return |
||||
} |
||||
|
||||
configValue, success := getValueFromRequest(w, r) |
||||
if !success { |
||||
return |
||||
} |
||||
|
||||
if err := data.SetServerSummary(configValue.Value.(string)); err != nil { |
||||
controllers.WriteSimpleResponse(w, false, err.Error()) |
||||
return |
||||
} |
||||
|
||||
// Update Fediverse followers about this change.
|
||||
if err := outbox.UpdateFollowersWithAccountUpdates(); err != nil { |
||||
controllers.WriteSimpleResponse(w, false, err.Error()) |
||||
return |
||||
} |
||||
|
||||
controllers.WriteSimpleResponse(w, true, "changed") |
||||
} |
||||
|
||||
// SetCustomOfflineMessage will set a message to display when the server is offline.
|
||||
func SetCustomOfflineMessage(w http.ResponseWriter, r *http.Request) { |
||||
if !requirePOST(w, r) { |
||||
return |
||||
} |
||||
|
||||
configValue, success := getValueFromRequest(w, r) |
||||
if !success { |
||||
return |
||||
} |
||||
|
||||
if err := data.SetCustomOfflineMessage(strings.TrimSpace(configValue.Value.(string))); err != nil { |
||||
controllers.WriteSimpleResponse(w, false, err.Error()) |
||||
return |
||||
} |
||||
|
||||
controllers.WriteSimpleResponse(w, true, "changed") |
||||
} |
||||
|
||||
// SetServerWelcomeMessage will handle the web config request to set the welcome message text.
|
||||
func SetServerWelcomeMessage(w http.ResponseWriter, r *http.Request) { |
||||
if !requirePOST(w, r) { |
||||
return |
||||
} |
||||
|
||||
configValue, success := getValueFromRequest(w, r) |
||||
if !success { |
||||
return |
||||
} |
||||
|
||||
if err := data.SetServerWelcomeMessage(strings.TrimSpace(configValue.Value.(string))); err != nil { |
||||
controllers.WriteSimpleResponse(w, false, err.Error()) |
||||
return |
||||
} |
||||
|
||||
controllers.WriteSimpleResponse(w, true, "changed") |
||||
} |
||||
|
||||
// SetExtraPageContent will handle the web config request to set the page markdown content.
|
||||
func SetExtraPageContent(w http.ResponseWriter, r *http.Request) { |
||||
if !requirePOST(w, r) { |
||||
return |
||||
} |
||||
|
||||
configValue, success := getValueFromRequest(w, r) |
||||
if !success { |
||||
return |
||||
} |
||||
|
||||
if err := data.SetExtraPageBodyContent(configValue.Value.(string)); err != nil { |
||||
controllers.WriteSimpleResponse(w, false, err.Error()) |
||||
return |
||||
} |
||||
|
||||
controllers.WriteSimpleResponse(w, true, "changed") |
||||
} |
||||
|
||||
// SetAdminPassword will handle the web config request to set the server admin password.
|
||||
func SetAdminPassword(w http.ResponseWriter, r *http.Request) { |
||||
if !requirePOST(w, r) { |
||||
return |
||||
} |
||||
|
||||
configValue, success := getValueFromRequest(w, r) |
||||
if !success { |
||||
return |
||||
} |
||||
|
||||
if err := data.SetAdminPassword(configValue.Value.(string)); err != nil { |
||||
controllers.WriteSimpleResponse(w, false, err.Error()) |
||||
return |
||||
} |
||||
|
||||
controllers.WriteSimpleResponse(w, true, "changed") |
||||
} |
||||
|
||||
// SetLogo will handle a new logo image file being uploaded.
|
||||
func SetLogo(w http.ResponseWriter, r *http.Request) { |
||||
if !requirePOST(w, r) { |
||||
return |
||||
} |
||||
|
||||
configValue, success := getValueFromRequest(w, r) |
||||
if !success { |
||||
return |
||||
} |
||||
|
||||
value, ok := configValue.Value.(string) |
||||
if !ok { |
||||
controllers.WriteSimpleResponse(w, false, "unable to find image data") |
||||
return |
||||
} |
||||
bytes, extension, err := utils.DecodeBase64Image(value) |
||||
if err != nil { |
||||
controllers.WriteSimpleResponse(w, false, err.Error()) |
||||
return |
||||
} |
||||
|
||||
imgPath := filepath.Join("data", "logo"+extension) |
||||
if err := os.WriteFile(imgPath, bytes, 0o600); err != nil { |
||||
controllers.WriteSimpleResponse(w, false, err.Error()) |
||||
return |
||||
} |
||||
|
||||
if err := data.SetLogoPath("logo" + extension); err != nil { |
||||
controllers.WriteSimpleResponse(w, false, err.Error()) |
||||
return |
||||
} |
||||
|
||||
if err := data.SetLogoUniquenessString(shortid.MustGenerate()); err != nil { |
||||
log.Error("Error saving logo uniqueness string: ", err) |
||||
} |
||||
|
||||
// Update Fediverse followers about this change.
|
||||
if err := outbox.UpdateFollowersWithAccountUpdates(); err != nil { |
||||
controllers.WriteSimpleResponse(w, false, err.Error()) |
||||
return |
||||
} |
||||
|
||||
controllers.WriteSimpleResponse(w, true, "changed") |
||||
} |
||||
|
||||
// SetNSFW will handle the web config request to set the NSFW flag.
|
||||
func SetNSFW(w http.ResponseWriter, r *http.Request) { |
||||
if !requirePOST(w, r) { |
||||
return |
||||
} |
||||
|
||||
configValue, success := getValueFromRequest(w, r) |
||||
if !success { |
||||
return |
||||
} |
||||
|
||||
if err := data.SetNSFW(configValue.Value.(bool)); err != nil { |
||||
controllers.WriteSimpleResponse(w, false, err.Error()) |
||||
return |
||||
} |
||||
|
||||
controllers.WriteSimpleResponse(w, true, "changed") |
||||
} |
||||
|
||||
// SetFfmpegPath will handle the web config request to validate and set an updated copy of ffmpg.
|
||||
func SetFfmpegPath(w http.ResponseWriter, r *http.Request) { |
||||
if !requirePOST(w, r) { |
||||
return |
||||
} |
||||
|
||||
configValue, success := getValueFromRequest(w, r) |
||||
if !success { |
||||
return |
||||
} |
||||
|
||||
path := configValue.Value.(string) |
||||
if err := utils.VerifyFFMpegPath(path); err != nil { |
||||
controllers.WriteSimpleResponse(w, false, err.Error()) |
||||
return |
||||
} |
||||
|
||||
if err := data.SetFfmpegPath(configValue.Value.(string)); err != nil { |
||||
controllers.WriteSimpleResponse(w, false, err.Error()) |
||||
return |
||||
} |
||||
|
||||
controllers.WriteSimpleResponse(w, true, "changed") |
||||
} |
||||
|
||||
// SetWebServerPort will handle the web config request to set the server's HTTP port.
|
||||
func SetWebServerPort(w http.ResponseWriter, r *http.Request) { |
||||
if !requirePOST(w, r) { |
||||
return |
||||
} |
||||
|
||||
configValue, success := getValueFromRequest(w, r) |
||||
if !success { |
||||
return |
||||
} |
||||
|
||||
if port, ok := configValue.Value.(float64); ok { |
||||
if (port < 1) || (port > 65535) { |
||||
controllers.WriteSimpleResponse(w, false, "Port number must be between 1 and 65535") |
||||
return |
||||
} |
||||
if err := data.SetHTTPPortNumber(port); err != nil { |
||||
controllers.WriteSimpleResponse(w, false, err.Error()) |
||||
return |
||||
} |
||||
|
||||
controllers.WriteSimpleResponse(w, true, "HTTP port set") |
||||
return |
||||
} |
||||
|
||||
controllers.WriteSimpleResponse(w, false, "Invalid type or value, port must be a number") |
||||
} |
||||
|
||||
// SetWebServerIP will handle the web config request to set the server's HTTP listen address.
|
||||
func SetWebServerIP(w http.ResponseWriter, r *http.Request) { |
||||
if !requirePOST(w, r) { |
||||
return |
||||
} |
||||
|
||||
configValue, success := getValueFromRequest(w, r) |
||||
if !success { |
||||
return |
||||
} |
||||
|
||||
if input, ok := configValue.Value.(string); ok { |
||||
if ip := net.ParseIP(input); ip != nil { |
||||
if err := data.SetHTTPListenAddress(ip.String()); err != nil { |
||||
controllers.WriteSimpleResponse(w, false, err.Error()) |
||||
return |
||||
} |
||||
|
||||
controllers.WriteSimpleResponse(w, true, "HTTP listen address set") |
||||
return |
||||
} |
||||
|
||||
controllers.WriteSimpleResponse(w, false, "Invalid IP address") |
||||
return |
||||
} |
||||
controllers.WriteSimpleResponse(w, false, "Invalid type or value, IP address must be a string") |
||||
} |
||||
|
||||
// SetRTMPServerPort will handle the web config request to set the inbound RTMP port.
|
||||
func SetRTMPServerPort(w http.ResponseWriter, r *http.Request) { |
||||
if !requirePOST(w, r) { |
||||
return |
||||
} |
||||
|
||||
configValue, success := getValueFromRequest(w, r) |
||||
if !success { |
||||
return |
||||
} |
||||
|
||||
if err := data.SetRTMPPortNumber(configValue.Value.(float64)); err != nil { |
||||
controllers.WriteSimpleResponse(w, false, err.Error()) |
||||
return |
||||
} |
||||
|
||||
controllers.WriteSimpleResponse(w, true, "rtmp port set") |
||||
} |
||||
|
||||
// SetServerURL will handle the web config request to set the full server URL.
|
||||
func SetServerURL(w http.ResponseWriter, r *http.Request) { |
||||
if !requirePOST(w, r) { |
||||
return |
||||
} |
||||
|
||||
configValue, success := getValueFromRequest(w, r) |
||||
if !success { |
||||
return |
||||
} |
||||
|
||||
rawValue, ok := configValue.Value.(string) |
||||
if !ok { |
||||
controllers.WriteSimpleResponse(w, false, "could not read server url") |
||||
return |
||||
} |
||||
|
||||
serverHostString := utils.GetHostnameFromURLString(rawValue) |
||||
if serverHostString == "" { |
||||
controllers.WriteSimpleResponse(w, false, "server url value invalid") |
||||
return |
||||
} |
||||
|
||||
// Block Private IP URLs
|
||||
ipAddr, ipErr := netip.ParseAddr(utils.GetHostnameWithoutPortFromURLString(rawValue)) |
||||
|
||||
if ipErr == nil && ipAddr.IsPrivate() { |
||||
controllers.WriteSimpleResponse(w, false, "Server URL cannot be private") |
||||
return |
||||
} |
||||
|
||||
// Trim any trailing slash
|
||||
serverURL := strings.TrimRight(rawValue, "/") |
||||
|
||||
if err := data.SetServerURL(serverURL); err != nil { |
||||
controllers.WriteSimpleResponse(w, false, err.Error()) |
||||
return |
||||
} |
||||
|
||||
controllers.WriteSimpleResponse(w, true, "server url set") |
||||
} |
||||
|
||||
// SetSocketHostOverride will set the host override for the websocket.
|
||||
func SetSocketHostOverride(w http.ResponseWriter, r *http.Request) { |
||||
if !requirePOST(w, r) { |
||||
return |
||||
} |
||||
|
||||
configValue, success := getValueFromRequest(w, r) |
||||
if !success { |
||||
return |
||||
} |
||||
|
||||
if err := data.SetWebsocketOverrideHost(configValue.Value.(string)); err != nil { |
||||
controllers.WriteSimpleResponse(w, false, err.Error()) |
||||
return |
||||
} |
||||
|
||||
controllers.WriteSimpleResponse(w, true, "websocket host override set") |
||||
} |
||||
|
||||
// SetDirectoryEnabled will handle the web config request to enable or disable directory registration.
|
||||
func SetDirectoryEnabled(w http.ResponseWriter, r *http.Request) { |
||||
if !requirePOST(w, r) { |
||||
return |
||||
} |
||||
|
||||
configValue, success := getValueFromRequest(w, r) |
||||
if !success { |
||||
return |
||||
} |
||||
|
||||
if err := data.SetDirectoryEnabled(configValue.Value.(bool)); err != nil { |
||||
controllers.WriteSimpleResponse(w, false, err.Error()) |
||||
return |
||||
} |
||||
controllers.WriteSimpleResponse(w, true, "directory state changed") |
||||
} |
||||
|
||||
// SetStreamLatencyLevel will handle the web config request to set the stream latency level.
|
||||
func SetStreamLatencyLevel(w http.ResponseWriter, r *http.Request) { |
||||
if !requirePOST(w, r) { |
||||
return |
||||
} |
||||
|
||||
configValue, success := getValueFromRequest(w, r) |
||||
if !success { |
||||
return |
||||
} |
||||
|
||||
if err := data.SetStreamLatencyLevel(configValue.Value.(float64)); err != nil { |
||||
controllers.WriteSimpleResponse(w, false, "error setting stream latency "+err.Error()) |
||||
return |
||||
} |
||||
|
||||
controllers.WriteSimpleResponse(w, true, "set stream latency") |
||||
} |
||||
|
||||
// SetS3Configuration will handle the web config request to set the storage configuration.
|
||||
func SetS3Configuration(w http.ResponseWriter, r *http.Request) { |
||||
if !requirePOST(w, r) { |
||||
return |
||||
} |
||||
|
||||
type s3ConfigurationRequest struct { |
||||
Value models.S3 `json:"value"` |
||||
} |
||||
|
||||
decoder := json.NewDecoder(r.Body) |
||||
var newS3Config s3ConfigurationRequest |
||||
if err := decoder.Decode(&newS3Config); err != nil { |
||||
controllers.WriteSimpleResponse(w, false, "unable to update s3 config with provided values") |
||||
return |
||||
} |
||||
|
||||
if newS3Config.Value.Enabled { |
||||
if newS3Config.Value.Endpoint == "" || !utils.IsValidURL((newS3Config.Value.Endpoint)) { |
||||
controllers.WriteSimpleResponse(w, false, "s3 support requires an endpoint") |
||||
return |
||||
} |
||||
|
||||
if newS3Config.Value.AccessKey == "" || newS3Config.Value.Secret == "" { |
||||
controllers.WriteSimpleResponse(w, false, "s3 support requires an access key and secret") |
||||
return |
||||
} |
||||
|
||||
if newS3Config.Value.Region == "" { |
||||
controllers.WriteSimpleResponse(w, false, "s3 support requires a region and endpoint") |
||||
return |
||||
} |
||||
|
||||
if newS3Config.Value.Bucket == "" { |
||||
controllers.WriteSimpleResponse(w, false, "s3 support requires a bucket created for storing public video segments") |
||||
return |
||||
} |
||||
} |
||||
|
||||
if err := data.SetS3Config(newS3Config.Value); err != nil { |
||||
controllers.WriteSimpleResponse(w, false, err.Error()) |
||||
return |
||||
} |
||||
controllers.WriteSimpleResponse(w, true, "storage configuration changed") |
||||
} |
||||
|
||||
// SetStreamOutputVariants will handle the web config request to set the video output stream variants.
|
||||
func SetStreamOutputVariants(w http.ResponseWriter, r *http.Request) { |
||||
if !requirePOST(w, r) { |
||||
return |
||||
} |
||||
|
||||
type streamOutputVariantRequest struct { |
||||
Value []models.StreamOutputVariant `json:"value"` |
||||
} |
||||
|
||||
decoder := json.NewDecoder(r.Body) |
||||
var videoVariants streamOutputVariantRequest |
||||
if err := decoder.Decode(&videoVariants); err != nil { |
||||
controllers.WriteSimpleResponse(w, false, "unable to update video config with provided values "+err.Error()) |
||||
return |
||||
} |
||||
|
||||
if err := data.SetStreamOutputVariants(videoVariants.Value); err != nil { |
||||
controllers.WriteSimpleResponse(w, false, "unable to update video config with provided values "+err.Error()) |
||||
return |
||||
} |
||||
|
||||
controllers.WriteSimpleResponse(w, true, "stream output variants updated") |
||||
} |
||||
|
||||
// SetSocialHandles will handle the web config request to set the external social profile links.
|
||||
func SetSocialHandles(w http.ResponseWriter, r *http.Request) { |
||||
if !requirePOST(w, r) { |
||||
return |
||||
} |
||||
|
||||
type socialHandlesRequest struct { |
||||
Value []models.SocialHandle `json:"value"` |
||||
} |
||||
|
||||
decoder := json.NewDecoder(r.Body) |
||||
var socialHandles socialHandlesRequest |
||||
if err := decoder.Decode(&socialHandles); err != nil { |
||||
controllers.WriteSimpleResponse(w, false, "unable to update social handles with provided values") |
||||
return |
||||
} |
||||
|
||||
if err := data.SetSocialHandles(socialHandles.Value); err != nil { |
||||
controllers.WriteSimpleResponse(w, false, "unable to update social handles with provided values") |
||||
return |
||||
} |
||||
|
||||
// Update Fediverse followers about this change.
|
||||
if err := outbox.UpdateFollowersWithAccountUpdates(); err != nil { |
||||
controllers.WriteSimpleResponse(w, false, err.Error()) |
||||
return |
||||
} |
||||
|
||||
controllers.WriteSimpleResponse(w, true, "social handles updated") |
||||
} |
||||
|
||||
// SetChatDisabled will disable chat functionality.
|
||||
func SetChatDisabled(w http.ResponseWriter, r *http.Request) { |
||||
if !requirePOST(w, r) { |
||||
return |
||||
} |
||||
|
||||
configValue, success := getValueFromRequest(w, r) |
||||
if !success { |
||||
controllers.WriteSimpleResponse(w, false, "unable to update chat disabled") |
||||
return |
||||
} |
||||
|
||||
if err := data.SetChatDisabled(configValue.Value.(bool)); err != nil { |
||||
controllers.WriteSimpleResponse(w, false, err.Error()) |
||||
return |
||||
} |
||||
|
||||
controllers.WriteSimpleResponse(w, true, "chat disabled status updated") |
||||
} |
||||
|
||||
// SetVideoCodec will change the codec used for video encoding.
|
||||
func SetVideoCodec(w http.ResponseWriter, r *http.Request) { |
||||
if !requirePOST(w, r) { |
||||
return |
||||
} |
||||
|
||||
configValue, success := getValueFromRequest(w, r) |
||||
if !success { |
||||
controllers.WriteSimpleResponse(w, false, "unable to change video codec") |
||||
return |
||||
} |
||||
|
||||
if err := data.SetVideoCodec(configValue.Value.(string)); err != nil { |
||||
controllers.WriteSimpleResponse(w, false, "unable to update codec") |
||||
return |
||||
} |
||||
|
||||
controllers.WriteSimpleResponse(w, true, "video codec updated") |
||||
} |
||||
|
||||
// SetExternalActions will set the 3rd party actions for the web interface.
|
||||
func SetExternalActions(w http.ResponseWriter, r *http.Request) { |
||||
type externalActionsRequest struct { |
||||
Value []models.ExternalAction `json:"value"` |
||||
} |
||||
|
||||
decoder := json.NewDecoder(r.Body) |
||||
var actions externalActionsRequest |
||||
if err := decoder.Decode(&actions); err != nil { |
||||
controllers.WriteSimpleResponse(w, false, "unable to update external actions with provided values") |
||||
return |
||||
} |
||||
|
||||
if err := data.SetExternalActions(actions.Value); err != nil { |
||||
controllers.WriteSimpleResponse(w, false, "unable to update external actions with provided values") |
||||
return |
||||
} |
||||
|
||||
controllers.WriteSimpleResponse(w, true, "external actions update") |
||||
} |
||||
|
||||
// SetCustomStyles will set the CSS string we insert into the page.
|
||||
func SetCustomStyles(w http.ResponseWriter, r *http.Request) { |
||||
customStyles, success := getValueFromRequest(w, r) |
||||
if !success { |
||||
controllers.WriteSimpleResponse(w, false, "unable to update custom styles") |
||||
return |
||||
} |
||||
|
||||
if err := data.SetCustomStyles(customStyles.Value.(string)); err != nil { |
||||
controllers.WriteSimpleResponse(w, false, err.Error()) |
||||
return |
||||
} |
||||
|
||||
controllers.WriteSimpleResponse(w, true, "custom styles updated") |
||||
} |
||||
|
||||
// SetCustomJavascript will set the Javascript string we insert into the page.
|
||||
func SetCustomJavascript(w http.ResponseWriter, r *http.Request) { |
||||
customJavascript, success := getValueFromRequest(w, r) |
||||
if !success { |
||||
controllers.WriteSimpleResponse(w, false, "unable to update custom javascript") |
||||
return |
||||
} |
||||
|
||||
if err := data.SetCustomJavascript(customJavascript.Value.(string)); err != nil { |
||||
controllers.WriteSimpleResponse(w, false, err.Error()) |
||||
return |
||||
} |
||||
|
||||
controllers.WriteSimpleResponse(w, true, "custom styles updated") |
||||
} |
||||
|
||||
// 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 |
||||
} |
||||
|
||||
if err := data.SetForbiddenUsernameList(request.Value); err != nil { |
||||
controllers.WriteSimpleResponse(w, false, err.Error()) |
||||
return |
||||
} |
||||
|
||||
controllers.WriteSimpleResponse(w, true, "forbidden username list updated") |
||||
} |
||||
|
||||
// SetSuggestedUsernameList will set the list of suggested usernames that newly registered users are assigned if it isn't inferred otherwise (i.e. through a proxy).
|
||||
func SetSuggestedUsernameList(w http.ResponseWriter, r *http.Request) { |
||||
type suggestedUsernameListRequest struct { |
||||
Value []string `json:"value"` |
||||
} |
||||
|
||||
decoder := json.NewDecoder(r.Body) |
||||
var request suggestedUsernameListRequest |
||||
|
||||
if err := decoder.Decode(&request); err != nil { |
||||
controllers.WriteSimpleResponse(w, false, "unable to update suggested usernames with provided values") |
||||
return |
||||
} |
||||
|
||||
if err := data.SetSuggestedUsernamesList(request.Value); err != nil { |
||||
controllers.WriteSimpleResponse(w, false, err.Error()) |
||||
return |
||||
} |
||||
|
||||
controllers.WriteSimpleResponse(w, true, "suggested username list updated") |
||||
} |
||||
|
||||
// SetChatJoinMessagesEnabled will enable or disable the chat join messages.
|
||||
func SetChatJoinMessagesEnabled(w http.ResponseWriter, r *http.Request) { |
||||
if !requirePOST(w, r) { |
||||
return |
||||
} |
||||
|
||||
configValue, success := getValueFromRequest(w, r) |
||||
if !success { |
||||
controllers.WriteSimpleResponse(w, false, "unable to update chat join messages enabled") |
||||
return |
||||
} |
||||
|
||||
if err := data.SetChatJoinMessagesEnabled(configValue.Value.(bool)); err != nil { |
||||
controllers.WriteSimpleResponse(w, false, err.Error()) |
||||
return |
||||
} |
||||
|
||||
controllers.WriteSimpleResponse(w, true, "chat join message status updated") |
||||
} |
||||
|
||||
// SetHideViewerCount will enable or disable hiding the viewer count.
|
||||
func SetHideViewerCount(w http.ResponseWriter, r *http.Request) { |
||||
if !requirePOST(w, r) { |
||||
return |
||||
} |
||||
|
||||
configValue, success := getValueFromRequest(w, r) |
||||
if !success { |
||||
controllers.WriteSimpleResponse(w, false, "unable to update hiding viewer count") |
||||
return |
||||
} |
||||
|
||||
if err := data.SetHideViewerCount(configValue.Value.(bool)); err != nil { |
||||
controllers.WriteSimpleResponse(w, false, err.Error()) |
||||
return |
||||
} |
||||
|
||||
controllers.WriteSimpleResponse(w, true, "hide viewer count setting updated") |
||||
} |
||||
|
||||
// SetDisableSearchIndexing will set search indexing support.
|
||||
func SetDisableSearchIndexing(w http.ResponseWriter, r *http.Request) { |
||||
if !requirePOST(w, r) { |
||||
return |
||||
} |
||||
|
||||
configValue, success := getValueFromRequest(w, r) |
||||
if !success { |
||||
controllers.WriteSimpleResponse(w, false, "unable to update search indexing") |
||||
return |
||||
} |
||||
|
||||
if err := data.SetDisableSearchIndexing(configValue.Value.(bool)); err != nil { |
||||
controllers.WriteSimpleResponse(w, false, err.Error()) |
||||
return |
||||
} |
||||
|
||||
controllers.WriteSimpleResponse(w, true, "search indexing support updated") |
||||
} |
||||
|
||||
// SetVideoServingEndpoint will save the video serving endpoint.
|
||||
func SetVideoServingEndpoint(w http.ResponseWriter, r *http.Request) { |
||||
endpoint, success := getValueFromRequest(w, r) |
||||
if !success { |
||||
controllers.WriteSimpleResponse(w, false, "unable to update custom video serving endpoint") |
||||
return |
||||
} |
||||
|
||||
value, ok := endpoint.Value.(string) |
||||
if !ok { |
||||
controllers.WriteSimpleResponse(w, false, "unable to update custom video serving endpoint") |
||||
return |
||||
} |
||||
|
||||
if err := data.SetVideoServingEndpoint(value); err != nil { |
||||
controllers.WriteSimpleResponse(w, false, err.Error()) |
||||
return |
||||
} |
||||
|
||||
controllers.WriteSimpleResponse(w, true, "custom video serving endpoint updated") |
||||
} |
||||
|
||||
func requirePOST(w http.ResponseWriter, r *http.Request) bool { |
||||
if r.Method != controllers.POST { |
||||
controllers.WriteSimpleResponse(w, false, r.Method+" not supported") |
||||
return false |
||||
} |
||||
|
||||
return true |
||||
} |
||||
|
||||
func getValueFromRequest(w http.ResponseWriter, r *http.Request) (ConfigValue, bool) { |
||||
decoder := json.NewDecoder(r.Body) |
||||
var configValue ConfigValue |
||||
if err := decoder.Decode(&configValue); err != nil { |
||||
log.Warnln(err) |
||||
controllers.WriteSimpleResponse(w, false, "unable to parse new value") |
||||
return configValue, false |
||||
} |
||||
|
||||
return configValue, true |
||||
} |
||||
|
||||
func getValuesFromRequest(w http.ResponseWriter, r *http.Request) ([]ConfigValue, bool) { |
||||
var values []ConfigValue |
||||
|
||||
decoder := json.NewDecoder(r.Body) |
||||
var configValue ConfigValue |
||||
if err := decoder.Decode(&configValue); err != nil { |
||||
controllers.WriteSimpleResponse(w, false, "unable to parse array of values") |
||||
return values, false |
||||
} |
||||
|
||||
object := reflect.ValueOf(configValue.Value) |
||||
|
||||
for i := 0; i < object.Len(); i++ { |
||||
values = append(values, ConfigValue{Value: object.Index(i).Interface()}) |
||||
} |
||||
|
||||
return values, true |
||||
} |
||||
|
||||
// SetStreamKeys will set the valid stream keys.
|
||||
func SetStreamKeys(w http.ResponseWriter, r *http.Request) { |
||||
if !requirePOST(w, r) { |
||||
return |
||||
} |
||||
|
||||
type streamKeysRequest struct { |
||||
Value []models.StreamKey `json:"value"` |
||||
} |
||||
|
||||
decoder := json.NewDecoder(r.Body) |
||||
var streamKeys streamKeysRequest |
||||
if err := decoder.Decode(&streamKeys); err != nil { |
||||
controllers.WriteSimpleResponse(w, false, "unable to update stream keys with provided values") |
||||
return |
||||
} |
||||
|
||||
if len(streamKeys.Value) == 0 { |
||||
controllers.WriteSimpleResponse(w, false, "must provide at least one valid stream key") |
||||
return |
||||
} |
||||
|
||||
for _, streamKey := range streamKeys.Value { |
||||
if streamKey.Key == "" { |
||||
controllers.WriteSimpleResponse(w, false, "stream key cannot be empty") |
||||
return |
||||
} |
||||
} |
||||
|
||||
if err := data.SetStreamKeys(streamKeys.Value); err != nil { |
||||
controllers.WriteSimpleResponse(w, false, err.Error()) |
||||
return |
||||
} |
||||
|
||||
controllers.WriteSimpleResponse(w, true, "changed") |
||||
} |
@ -1,21 +0,0 @@
@@ -1,21 +0,0 @@
|
||||
package admin |
||||
|
||||
import ( |
||||
"net/http" |
||||
|
||||
"github.com/owncast/owncast/controllers" |
||||
"github.com/owncast/owncast/core" |
||||
|
||||
"github.com/owncast/owncast/core/rtmp" |
||||
) |
||||
|
||||
// DisconnectInboundConnection will force-disconnect an inbound stream.
|
||||
func DisconnectInboundConnection(w http.ResponseWriter, r *http.Request) { |
||||
if !core.GetStatus().Online { |
||||
controllers.WriteSimpleResponse(w, false, "no inbound stream connected") |
||||
return |
||||
} |
||||
|
||||
rtmp.Disconnect() |
||||
controllers.WriteSimpleResponse(w, true, "inbound stream disconnected") |
||||
} |
@ -1,179 +0,0 @@
@@ -1,179 +0,0 @@
|
||||
package admin |
||||
|
||||
import ( |
||||
"net/http" |
||||
|
||||
"github.com/owncast/owncast/activitypub" |
||||
"github.com/owncast/owncast/activitypub/outbox" |
||||
"github.com/owncast/owncast/activitypub/persistence" |
||||
"github.com/owncast/owncast/controllers" |
||||
"github.com/owncast/owncast/core/data" |
||||
) |
||||
|
||||
// SendFederatedMessage will send a manual message to the fediverse.
|
||||
func SendFederatedMessage(w http.ResponseWriter, r *http.Request) { |
||||
if !requirePOST(w, r) { |
||||
return |
||||
} |
||||
|
||||
configValue, success := getValueFromRequest(w, r) |
||||
if !success { |
||||
return |
||||
} |
||||
|
||||
message, ok := configValue.Value.(string) |
||||
if !ok { |
||||
controllers.WriteSimpleResponse(w, false, "unable to send message") |
||||
return |
||||
} |
||||
|
||||
if err := activitypub.SendPublicFederatedMessage(message); err != nil { |
||||
controllers.WriteSimpleResponse(w, false, err.Error()) |
||||
return |
||||
} |
||||
|
||||
controllers.WriteSimpleResponse(w, true, "sent") |
||||
} |
||||
|
||||
// SetFederationEnabled will set if Federation features are enabled.
|
||||
func SetFederationEnabled(w http.ResponseWriter, r *http.Request) { |
||||
if !requirePOST(w, r) { |
||||
return |
||||
} |
||||
|
||||
configValue, success := getValueFromRequest(w, r) |
||||
if !success { |
||||
return |
||||
} |
||||
|
||||
if err := data.SetFederationEnabled(configValue.Value.(bool)); err != nil { |
||||
controllers.WriteSimpleResponse(w, false, err.Error()) |
||||
return |
||||
} |
||||
controllers.WriteSimpleResponse(w, true, "federation features saved") |
||||
} |
||||
|
||||
// SetFederationActivityPrivate will set if Federation features are private to followers.
|
||||
func SetFederationActivityPrivate(w http.ResponseWriter, r *http.Request) { |
||||
if !requirePOST(w, r) { |
||||
return |
||||
} |
||||
|
||||
configValue, success := getValueFromRequest(w, r) |
||||
if !success { |
||||
return |
||||
} |
||||
|
||||
if err := data.SetFederationIsPrivate(configValue.Value.(bool)); err != nil { |
||||
controllers.WriteSimpleResponse(w, false, err.Error()) |
||||
return |
||||
} |
||||
|
||||
// Update Fediverse followers about this change.
|
||||
if err := outbox.UpdateFollowersWithAccountUpdates(); err != nil { |
||||
controllers.WriteSimpleResponse(w, false, err.Error()) |
||||
return |
||||
} |
||||
|
||||
controllers.WriteSimpleResponse(w, true, "federation private saved") |
||||
} |
||||
|
||||
// SetFederationShowEngagement will set if Fedivese engagement shows in chat.
|
||||
func SetFederationShowEngagement(w http.ResponseWriter, r *http.Request) { |
||||
if !requirePOST(w, r) { |
||||
return |
||||
} |
||||
|
||||
configValue, success := getValueFromRequest(w, r) |
||||
if !success { |
||||
return |
||||
} |
||||
|
||||
if err := data.SetFederationShowEngagement(configValue.Value.(bool)); err != nil { |
||||
controllers.WriteSimpleResponse(w, false, err.Error()) |
||||
return |
||||
} |
||||
controllers.WriteSimpleResponse(w, true, "federation show engagement saved") |
||||
} |
||||
|
||||
// SetFederationUsername will set the local actor username used for federation activities.
|
||||
func SetFederationUsername(w http.ResponseWriter, r *http.Request) { |
||||
if !requirePOST(w, r) { |
||||
return |
||||
} |
||||
|
||||
configValue, success := getValueFromRequest(w, r) |
||||
if !success { |
||||
return |
||||
} |
||||
|
||||
if err := data.SetFederationUsername(configValue.Value.(string)); err != nil { |
||||
controllers.WriteSimpleResponse(w, false, err.Error()) |
||||
return |
||||
} |
||||
|
||||
controllers.WriteSimpleResponse(w, true, "username saved") |
||||
} |
||||
|
||||
// SetFederationGoLiveMessage will set the federated message sent when the streamer goes live.
|
||||
func SetFederationGoLiveMessage(w http.ResponseWriter, r *http.Request) { |
||||
if !requirePOST(w, r) { |
||||
return |
||||
} |
||||
|
||||
configValue, success := getValueFromRequest(w, r) |
||||
if !success { |
||||
return |
||||
} |
||||
|
||||
if err := data.SetFederationGoLiveMessage(configValue.Value.(string)); err != nil { |
||||
controllers.WriteSimpleResponse(w, false, err.Error()) |
||||
return |
||||
} |
||||
|
||||
controllers.WriteSimpleResponse(w, true, "message saved") |
||||
} |
||||
|
||||
// SetFederationBlockDomains saves a list of domains to block on the Fediverse.
|
||||
func SetFederationBlockDomains(w http.ResponseWriter, r *http.Request) { |
||||
if !requirePOST(w, r) { |
||||
return |
||||
} |
||||
|
||||
configValues, success := getValuesFromRequest(w, r) |
||||
if !success { |
||||
controllers.WriteSimpleResponse(w, false, "unable to handle provided domains") |
||||
return |
||||
} |
||||
|
||||
domainStrings := make([]string, 0) |
||||
for _, domain := range configValues { |
||||
domainStrings = append(domainStrings, domain.Value.(string)) |
||||
} |
||||
|
||||
if err := data.SetBlockedFederatedDomains(domainStrings); err != nil { |
||||
controllers.WriteSimpleResponse(w, false, err.Error()) |
||||
return |
||||
} |
||||
|
||||
controllers.WriteSimpleResponse(w, true, "saved") |
||||
} |
||||
|
||||
// GetFederatedActions will return the saved list of accepted inbound
|
||||
// federated activities.
|
||||
func GetFederatedActions(page int, pageSize int, w http.ResponseWriter, r *http.Request) { |
||||
offset := pageSize * page |
||||
|
||||
activities, total, err := persistence.GetInboundActivities(pageSize, offset) |
||||
if err != nil { |
||||
controllers.WriteSimpleResponse(w, false, err.Error()) |
||||
return |
||||
} |
||||
|
||||
response := controllers.PaginatedResponse{ |
||||
Total: total, |
||||
Results: activities, |
||||
} |
||||
|
||||
controllers.WriteResponse(w, response) |
||||
} |
@ -1,60 +0,0 @@
@@ -1,60 +0,0 @@
|
||||
package admin |
||||
|
||||
import ( |
||||
"encoding/json" |
||||
"net/http" |
||||
|
||||
"github.com/owncast/owncast/controllers" |
||||
"github.com/owncast/owncast/core/data" |
||||
"github.com/owncast/owncast/models" |
||||
) |
||||
|
||||
// SetDiscordNotificationConfiguration will set the discord notification configuration.
|
||||
func SetDiscordNotificationConfiguration(w http.ResponseWriter, r *http.Request) { |
||||
if !requirePOST(w, r) { |
||||
return |
||||
} |
||||
|
||||
type request struct { |
||||
Value models.DiscordConfiguration `json:"value"` |
||||
} |
||||
|
||||
decoder := json.NewDecoder(r.Body) |
||||
var config request |
||||
if err := decoder.Decode(&config); err != nil { |
||||
controllers.WriteSimpleResponse(w, false, "unable to update discord config with provided values") |
||||
return |
||||
} |
||||
|
||||
if err := data.SetDiscordConfig(config.Value); err != nil { |
||||
controllers.WriteSimpleResponse(w, false, "unable to update discord config with provided values") |
||||
return |
||||
} |
||||
|
||||
controllers.WriteSimpleResponse(w, true, "updated discord config with provided values") |
||||
} |
||||
|
||||
// SetBrowserNotificationConfiguration will set the browser notification configuration.
|
||||
func SetBrowserNotificationConfiguration(w http.ResponseWriter, r *http.Request) { |
||||
if !requirePOST(w, r) { |
||||
return |
||||
} |
||||
|
||||
type request struct { |
||||
Value models.BrowserNotificationConfiguration `json:"value"` |
||||
} |
||||
|
||||
decoder := json.NewDecoder(r.Body) |
||||
var config request |
||||
if err := decoder.Decode(&config); err != nil { |
||||
controllers.WriteSimpleResponse(w, false, "unable to update browser push config with provided values") |
||||
return |
||||
} |
||||
|
||||
if err := data.SetBrowserPushConfig(config.Value); err != nil { |
||||
controllers.WriteSimpleResponse(w, false, "unable to update browser push config with provided values") |
||||
return |
||||
} |
||||
|
||||
controllers.WriteSimpleResponse(w, true, "updated browser push config with provided values") |
||||
} |
@ -1,7 +0,0 @@
@@ -1,7 +0,0 @@
|
||||
package controllers |
||||
|
||||
// POST is the HTTP POST method.
|
||||
const POST = "POST" |
||||
|
||||
// GET is the HTTP GET method.
|
||||
const GET = "GET" |
File diff suppressed because one or more lines are too long
@ -1,444 +0,0 @@
@@ -1,444 +0,0 @@
|
||||
package router |
||||
|
||||
import ( |
||||
"fmt" |
||||
"net/http" |
||||
"time" |
||||
|
||||
"github.com/CAFxX/httpcompression" |
||||
"github.com/prometheus/client_golang/prometheus/promhttp" |
||||
log "github.com/sirupsen/logrus" |
||||
"golang.org/x/net/http2" |
||||
"golang.org/x/net/http2/h2c" |
||||
|
||||
"github.com/owncast/owncast/activitypub" |
||||
"github.com/owncast/owncast/config" |
||||
"github.com/owncast/owncast/controllers" |
||||
"github.com/owncast/owncast/controllers/admin" |
||||
fediverseauth "github.com/owncast/owncast/controllers/auth/fediverse" |
||||
"github.com/owncast/owncast/controllers/auth/indieauth" |
||||
"github.com/owncast/owncast/controllers/moderation" |
||||
"github.com/owncast/owncast/core/chat" |
||||
"github.com/owncast/owncast/core/data" |
||||
"github.com/owncast/owncast/core/user" |
||||
"github.com/owncast/owncast/router/middleware" |
||||
"github.com/owncast/owncast/utils" |
||||
"github.com/owncast/owncast/yp" |
||||
) |
||||
|
||||
// Start starts the router for the http, ws, and rtmp.
|
||||
func Start() error { |
||||
// The primary web app.
|
||||
http.HandleFunc("/", controllers.IndexHandler) |
||||
|
||||
// The admin web app.
|
||||
http.HandleFunc("/admin/", middleware.RequireAdminAuth(controllers.IndexHandler)) |
||||
|
||||
// Images
|
||||
http.HandleFunc("/thumbnail.jpg", controllers.GetThumbnail) |
||||
http.HandleFunc("/preview.gif", controllers.GetPreview) |
||||
http.HandleFunc("/logo", controllers.GetLogo) |
||||
|
||||
// Custom Javascript
|
||||
http.HandleFunc("/customjavascript", controllers.ServeCustomJavascript) |
||||
|
||||
// Return a single emoji image.
|
||||
http.HandleFunc(config.EmojiDir, controllers.GetCustomEmojiImage) |
||||
|
||||
// return the logo
|
||||
|
||||
// return a logo that's compatible with external social networks
|
||||
http.HandleFunc("/logo/external", controllers.GetCompatibleLogo) |
||||
|
||||
// robots.txt
|
||||
http.HandleFunc("/robots.txt", controllers.GetRobotsDotTxt) |
||||
|
||||
// status of the system
|
||||
http.HandleFunc("/api/status", controllers.GetStatus) |
||||
|
||||
// custom emoji supported in the chat
|
||||
http.HandleFunc("/api/emoji", controllers.GetCustomEmojiList) |
||||
|
||||
// chat rest api
|
||||
http.HandleFunc("/api/chat", middleware.RequireUserAccessToken(controllers.GetChatMessages)) |
||||
|
||||
// web config api
|
||||
http.HandleFunc("/api/config", controllers.GetWebConfig) |
||||
|
||||
// return the YP protocol data
|
||||
http.HandleFunc("/api/yp", yp.GetYPResponse) |
||||
|
||||
// list of all social platforms
|
||||
http.HandleFunc("/api/socialplatforms", controllers.GetAllSocialPlatforms) |
||||
|
||||
// return the list of video variants available
|
||||
http.HandleFunc("/api/video/variants", controllers.GetVideoStreamOutputVariants) |
||||
|
||||
// tell the backend you're an active viewer
|
||||
http.HandleFunc("/api/ping", controllers.Ping) |
||||
|
||||
// register a new chat user
|
||||
http.HandleFunc("/api/chat/register", controllers.RegisterAnonymousChatUser) |
||||
|
||||
// return remote follow details
|
||||
http.HandleFunc("/api/remotefollow", controllers.RemoteFollow) |
||||
|
||||
// return followers
|
||||
http.HandleFunc("/api/followers", middleware.HandlePagination(controllers.GetFollowers)) |
||||
|
||||
// save client video playback metrics
|
||||
http.HandleFunc("/api/metrics/playback", controllers.ReportPlaybackMetrics) |
||||
|
||||
// Register for notifications
|
||||
http.HandleFunc("/api/notifications/register", middleware.RequireUserAccessToken(controllers.RegisterForLiveNotifications)) |
||||
|
||||
// Authenticated admin requests
|
||||
|
||||
// Current inbound broadcaster
|
||||
http.HandleFunc("/api/admin/status", middleware.RequireAdminAuth(admin.Status)) |
||||
|
||||
// Return HLS video
|
||||
http.HandleFunc("/hls/", controllers.HandleHLSRequest) |
||||
|
||||
// Disconnect inbound stream
|
||||
http.HandleFunc("/api/admin/disconnect", middleware.RequireAdminAuth(admin.DisconnectInboundConnection)) |
||||
|
||||
// Server config
|
||||
http.HandleFunc("/api/admin/serverconfig", middleware.RequireAdminAuth(admin.GetServerConfig)) |
||||
|
||||
// Get viewer count over time
|
||||
http.HandleFunc("/api/admin/viewersOverTime", middleware.RequireAdminAuth(admin.GetViewersOverTime)) |
||||
|
||||
// Get active viewers
|
||||
http.HandleFunc("/api/admin/viewers", middleware.RequireAdminAuth(admin.GetActiveViewers)) |
||||
|
||||
// Get hardware stats
|
||||
http.HandleFunc("/api/admin/hardwarestats", middleware.RequireAdminAuth(admin.GetHardwareStats)) |
||||
|
||||
// Get a a detailed list of currently connected chat clients
|
||||
http.HandleFunc("/api/admin/chat/clients", middleware.RequireAdminAuth(admin.GetConnectedChatClients)) |
||||
|
||||
// Get all logs
|
||||
http.HandleFunc("/api/admin/logs", middleware.RequireAdminAuth(admin.GetLogs)) |
||||
|
||||
// Get warning/error logs
|
||||
http.HandleFunc("/api/admin/logs/warnings", middleware.RequireAdminAuth(admin.GetWarnings)) |
||||
|
||||
// Get all chat messages for the admin, unfiltered.
|
||||
http.HandleFunc("/api/admin/chat/messages", middleware.RequireAdminAuth(admin.GetChatMessages)) |
||||
|
||||
// Update chat message visibility
|
||||
http.HandleFunc("/api/admin/chat/messagevisibility", middleware.RequireAdminAuth(admin.UpdateMessageVisibility)) |
||||
|
||||
// Enable/disable a user
|
||||
http.HandleFunc("/api/admin/chat/users/setenabled", middleware.RequireAdminAuth(admin.UpdateUserEnabled)) |
||||
|
||||
// Ban/unban an IP address
|
||||
http.HandleFunc("/api/admin/chat/users/ipbans/create", middleware.RequireAdminAuth(admin.BanIPAddress)) |
||||
|
||||
// Remove an IP address ban
|
||||
http.HandleFunc("/api/admin/chat/users/ipbans/remove", middleware.RequireAdminAuth(admin.UnBanIPAddress)) |
||||
|
||||
// Return all the banned IP addresses
|
||||
http.HandleFunc("/api/admin/chat/users/ipbans", middleware.RequireAdminAuth(admin.GetIPAddressBans)) |
||||
|
||||
// Get a list of disabled users
|
||||
http.HandleFunc("/api/admin/chat/users/disabled", middleware.RequireAdminAuth(admin.GetDisabledUsers)) |
||||
|
||||
// Set moderator status for a user
|
||||
http.HandleFunc("/api/admin/chat/users/setmoderator", middleware.RequireAdminAuth(admin.UpdateUserModerator)) |
||||
|
||||
// Get a list of moderator users
|
||||
http.HandleFunc("/api/admin/chat/users/moderators", middleware.RequireAdminAuth(admin.GetModerators)) |
||||
|
||||
// return followers
|
||||
http.HandleFunc("/api/admin/followers", middleware.RequireAdminAuth(middleware.HandlePagination(controllers.GetFollowers))) |
||||
|
||||
// Get a list of pending follow requests
|
||||
http.HandleFunc("/api/admin/followers/pending", middleware.RequireAdminAuth(admin.GetPendingFollowRequests)) |
||||
|
||||
// Get a list of rejected or blocked follows
|
||||
http.HandleFunc("/api/admin/followers/blocked", middleware.RequireAdminAuth(admin.GetBlockedAndRejectedFollowers)) |
||||
|
||||
// Set the following state of a follower or follow request.
|
||||
http.HandleFunc("/api/admin/followers/approve", middleware.RequireAdminAuth(admin.ApproveFollower)) |
||||
|
||||
// Upload custom emoji
|
||||
http.HandleFunc("/api/admin/emoji/upload", middleware.RequireAdminAuth(admin.UploadCustomEmoji)) |
||||
|
||||
// Delete custom emoji
|
||||
http.HandleFunc("/api/admin/emoji/delete", middleware.RequireAdminAuth(admin.DeleteCustomEmoji)) |
||||
|
||||
// Update config values
|
||||
|
||||
// Change the current streaming key in memory
|
||||
http.HandleFunc("/api/admin/config/adminpass", middleware.RequireAdminAuth(admin.SetAdminPassword)) |
||||
|
||||
// Set an array of valid stream keys
|
||||
http.HandleFunc("/api/admin/config/streamkeys", middleware.RequireAdminAuth(admin.SetStreamKeys)) |
||||
|
||||
// Change the extra page content in memory
|
||||
http.HandleFunc("/api/admin/config/pagecontent", middleware.RequireAdminAuth(admin.SetExtraPageContent)) |
||||
|
||||
// Stream title
|
||||
http.HandleFunc("/api/admin/config/streamtitle", middleware.RequireAdminAuth(admin.SetStreamTitle)) |
||||
|
||||
// Server name
|
||||
http.HandleFunc("/api/admin/config/name", middleware.RequireAdminAuth(admin.SetServerName)) |
||||
|
||||
// Server summary
|
||||
http.HandleFunc("/api/admin/config/serversummary", middleware.RequireAdminAuth(admin.SetServerSummary)) |
||||
|
||||
// Offline message
|
||||
http.HandleFunc("/api/admin/config/offlinemessage", middleware.RequireAdminAuth(admin.SetCustomOfflineMessage)) |
||||
|
||||
// Server welcome message
|
||||
http.HandleFunc("/api/admin/config/welcomemessage", middleware.RequireAdminAuth(admin.SetServerWelcomeMessage)) |
||||
|
||||
// Disable chat
|
||||
http.HandleFunc("/api/admin/config/chat/disable", middleware.RequireAdminAuth(admin.SetChatDisabled)) |
||||
|
||||
// Disable chat user join messages
|
||||
http.HandleFunc("/api/admin/config/chat/joinmessagesenabled", middleware.RequireAdminAuth(admin.SetChatJoinMessagesEnabled)) |
||||
|
||||
// Enable/disable chat established user mode
|
||||
http.HandleFunc("/api/admin/config/chat/establishedusermode", middleware.RequireAdminAuth(admin.SetEnableEstablishedChatUserMode)) |
||||
|
||||
// Set chat usernames that are not allowed
|
||||
http.HandleFunc("/api/admin/config/chat/forbiddenusernames", middleware.RequireAdminAuth(admin.SetForbiddenUsernameList)) |
||||
|
||||
// Set the suggested chat usernames that will be assigned automatically
|
||||
http.HandleFunc("/api/admin/config/chat/suggestedusernames", middleware.RequireAdminAuth(admin.SetSuggestedUsernameList)) |
||||
|
||||
// Set video codec
|
||||
http.HandleFunc("/api/admin/config/video/codec", middleware.RequireAdminAuth(admin.SetVideoCodec)) |
||||
|
||||
// Set style/color/css values
|
||||
http.HandleFunc("/api/admin/config/appearance", middleware.RequireAdminAuth(admin.SetCustomColorVariableValues)) |
||||
|
||||
// Return all webhooks
|
||||
http.HandleFunc("/api/admin/webhooks", middleware.RequireAdminAuth(admin.GetWebhooks)) |
||||
|
||||
// Delete a single webhook
|
||||
http.HandleFunc("/api/admin/webhooks/delete", middleware.RequireAdminAuth(admin.DeleteWebhook)) |
||||
|
||||
// Create a single webhook
|
||||
http.HandleFunc("/api/admin/webhooks/create", middleware.RequireAdminAuth(admin.CreateWebhook)) |
||||
|
||||
// Get all access tokens
|
||||
http.HandleFunc("/api/admin/accesstokens", middleware.RequireAdminAuth(admin.GetExternalAPIUsers)) |
||||
|
||||
// Delete a single access token
|
||||
http.HandleFunc("/api/admin/accesstokens/delete", middleware.RequireAdminAuth(admin.DeleteExternalAPIUser)) |
||||
|
||||
// Create a single access token
|
||||
http.HandleFunc("/api/admin/accesstokens/create", middleware.RequireAdminAuth(admin.CreateExternalAPIUser)) |
||||
|
||||
// Return the auto-update features that are supported for this instance.
|
||||
http.HandleFunc("/api/admin/update/options", middleware.RequireAdminAuth(admin.AutoUpdateOptions)) |
||||
|
||||
// Begin the auto update
|
||||
http.HandleFunc("/api/admin/update/start", middleware.RequireAdminAuth(admin.AutoUpdateStart)) |
||||
|
||||
// Force quit the service to restart it
|
||||
http.HandleFunc("/api/admin/update/forcequit", middleware.RequireAdminAuth(admin.AutoUpdateForceQuit)) |
||||
|
||||
// Send a system message to chat
|
||||
http.HandleFunc("/api/integrations/chat/system", middleware.RequireExternalAPIAccessToken(user.ScopeCanSendSystemMessages, admin.SendSystemMessage)) |
||||
|
||||
// Send a system message to a single client
|
||||
http.HandleFunc(utils.RestEndpoint("/api/integrations/chat/system/client/{clientId}", middleware.RequireExternalAPIAccessToken(user.ScopeCanSendSystemMessages, admin.SendSystemMessageToConnectedClient))) |
||||
|
||||
// Send a user message to chat *NO LONGER SUPPORTED
|
||||
http.HandleFunc("/api/integrations/chat/user", middleware.RequireExternalAPIAccessToken(user.ScopeCanSendChatMessages, admin.SendUserMessage)) |
||||
|
||||
// Send a message to chat as a specific 3rd party bot/integration based on its access token
|
||||
http.HandleFunc("/api/integrations/chat/send", middleware.RequireExternalAPIAccessToken(user.ScopeCanSendChatMessages, admin.SendIntegrationChatMessage)) |
||||
|
||||
// Send a user action to chat
|
||||
http.HandleFunc("/api/integrations/chat/action", middleware.RequireExternalAPIAccessToken(user.ScopeCanSendSystemMessages, admin.SendChatAction)) |
||||
|
||||
// Hide chat message
|
||||
http.HandleFunc("/api/integrations/chat/messagevisibility", middleware.RequireExternalAPIAccessToken(user.ScopeHasAdminAccess, admin.ExternalUpdateMessageVisibility)) |
||||
|
||||
// Stream title
|
||||
http.HandleFunc("/api/integrations/streamtitle", middleware.RequireExternalAPIAccessToken(user.ScopeHasAdminAccess, admin.ExternalSetStreamTitle)) |
||||
|
||||
// Get chat history
|
||||
http.HandleFunc("/api/integrations/chat", middleware.RequireExternalAPIAccessToken(user.ScopeHasAdminAccess, controllers.ExternalGetChatMessages)) |
||||
|
||||
// Connected clients
|
||||
http.HandleFunc("/api/integrations/clients", middleware.RequireExternalAPIAccessToken(user.ScopeHasAdminAccess, admin.ExternalGetConnectedChatClients)) |
||||
|
||||
// Logo path
|
||||
http.HandleFunc("/api/admin/config/logo", middleware.RequireAdminAuth(admin.SetLogo)) |
||||
|
||||
// Server tags
|
||||
http.HandleFunc("/api/admin/config/tags", middleware.RequireAdminAuth(admin.SetTags)) |
||||
|
||||
// ffmpeg
|
||||
http.HandleFunc("/api/admin/config/ffmpegpath", middleware.RequireAdminAuth(admin.SetFfmpegPath)) |
||||
|
||||
// Server http port
|
||||
http.HandleFunc("/api/admin/config/webserverport", middleware.RequireAdminAuth(admin.SetWebServerPort)) |
||||
|
||||
// Server http listen address
|
||||
http.HandleFunc("/api/admin/config/webserverip", middleware.RequireAdminAuth(admin.SetWebServerIP)) |
||||
|
||||
// Server rtmp port
|
||||
http.HandleFunc("/api/admin/config/rtmpserverport", middleware.RequireAdminAuth(admin.SetRTMPServerPort)) |
||||
|
||||
// Websocket host override
|
||||
http.HandleFunc("/api/admin/config/sockethostoverride", middleware.RequireAdminAuth(admin.SetSocketHostOverride)) |
||||
|
||||
// Custom video serving endpoint
|
||||
http.HandleFunc("/api/admin/config/videoservingendpoint", middleware.RequireAdminAuth(admin.SetVideoServingEndpoint)) |
||||
|
||||
// Is server marked as NSFW
|
||||
http.HandleFunc("/api/admin/config/nsfw", middleware.RequireAdminAuth(admin.SetNSFW)) |
||||
|
||||
// directory enabled
|
||||
http.HandleFunc("/api/admin/config/directoryenabled", middleware.RequireAdminAuth(admin.SetDirectoryEnabled)) |
||||
|
||||
// social handles
|
||||
http.HandleFunc("/api/admin/config/socialhandles", middleware.RequireAdminAuth(admin.SetSocialHandles)) |
||||
|
||||
// set the number of video segments and duration per segment in a playlist
|
||||
http.HandleFunc("/api/admin/config/video/streamlatencylevel", middleware.RequireAdminAuth(admin.SetStreamLatencyLevel)) |
||||
|
||||
// set an array of video output configurations
|
||||
http.HandleFunc("/api/admin/config/video/streamoutputvariants", middleware.RequireAdminAuth(admin.SetStreamOutputVariants)) |
||||
|
||||
// set s3 configuration
|
||||
http.HandleFunc("/api/admin/config/s3", middleware.RequireAdminAuth(admin.SetS3Configuration)) |
||||
|
||||
// set server url
|
||||
http.HandleFunc("/api/admin/config/serverurl", middleware.RequireAdminAuth(admin.SetServerURL)) |
||||
|
||||
// reset the YP registration
|
||||
http.HandleFunc("/api/admin/yp/reset", middleware.RequireAdminAuth(admin.ResetYPRegistration)) |
||||
|
||||
// set external action links
|
||||
http.HandleFunc("/api/admin/config/externalactions", middleware.RequireAdminAuth(admin.SetExternalActions)) |
||||
|
||||
// set custom style css
|
||||
http.HandleFunc("/api/admin/config/customstyles", middleware.RequireAdminAuth(admin.SetCustomStyles)) |
||||
|
||||
// set custom style javascript
|
||||
http.HandleFunc("/api/admin/config/customjavascript", middleware.RequireAdminAuth(admin.SetCustomJavascript)) |
||||
|
||||
// Video playback metrics
|
||||
http.HandleFunc("/api/admin/metrics/video", middleware.RequireAdminAuth(admin.GetVideoPlaybackMetrics)) |
||||
|
||||
// Is the viewer count hidden from viewers
|
||||
http.HandleFunc("/api/admin/config/hideviewercount", middleware.RequireAdminAuth(admin.SetHideViewerCount)) |
||||
|
||||
// set disabling of search indexing
|
||||
http.HandleFunc("/api/admin/config/disablesearchindexing", middleware.RequireAdminAuth(admin.SetDisableSearchIndexing)) |
||||
|
||||
// Inline chat moderation actions
|
||||
|
||||
// Update chat message visibility
|
||||
http.HandleFunc("/api/chat/messagevisibility", middleware.RequireUserModerationScopeAccesstoken(admin.UpdateMessageVisibility)) |
||||
|
||||
// Enable/disable a user
|
||||
http.HandleFunc("/api/chat/users/setenabled", middleware.RequireUserModerationScopeAccesstoken(admin.UpdateUserEnabled)) |
||||
|
||||
// Get a user's details
|
||||
http.HandleFunc("/api/moderation/chat/user/", middleware.RequireUserModerationScopeAccesstoken(moderation.GetUserDetails)) |
||||
|
||||
// Configure Federation features
|
||||
|
||||
// enable/disable federation features
|
||||
http.HandleFunc("/api/admin/config/federation/enable", middleware.RequireAdminAuth(admin.SetFederationEnabled)) |
||||
|
||||
// set if federation activities are private
|
||||
http.HandleFunc("/api/admin/config/federation/private", middleware.RequireAdminAuth(admin.SetFederationActivityPrivate)) |
||||
|
||||
// set if fediverse engagement appears in chat
|
||||
http.HandleFunc("/api/admin/config/federation/showengagement", middleware.RequireAdminAuth(admin.SetFederationShowEngagement)) |
||||
|
||||
// set local federated username
|
||||
http.HandleFunc("/api/admin/config/federation/username", middleware.RequireAdminAuth(admin.SetFederationUsername)) |
||||
|
||||
// set federated go live message
|
||||
http.HandleFunc("/api/admin/config/federation/livemessage", middleware.RequireAdminAuth(admin.SetFederationGoLiveMessage)) |
||||
|
||||
// Federation blocked domains
|
||||
http.HandleFunc("/api/admin/config/federation/blockdomains", middleware.RequireAdminAuth(admin.SetFederationBlockDomains)) |
||||
|
||||
// send a public message to the Fediverse from the server's user
|
||||
http.HandleFunc("/api/admin/federation/send", middleware.RequireAdminAuth(admin.SendFederatedMessage)) |
||||
|
||||
// Return federated activities
|
||||
http.HandleFunc("/api/admin/federation/actions", middleware.RequireAdminAuth(middleware.HandlePagination(admin.GetFederatedActions))) |
||||
|
||||
// Prometheus metrics
|
||||
http.Handle("/api/admin/prometheus", middleware.RequireAdminAuth(func(rw http.ResponseWriter, r *http.Request) { |
||||
promhttp.Handler().ServeHTTP(rw, r) |
||||
})) |
||||
|
||||
// Configure outbound notification channels.
|
||||
http.HandleFunc("/api/admin/config/notifications/discord", middleware.RequireAdminAuth(admin.SetDiscordNotificationConfiguration)) |
||||
http.HandleFunc("/api/admin/config/notifications/browser", middleware.RequireAdminAuth(admin.SetBrowserNotificationConfiguration)) |
||||
|
||||
// Auth
|
||||
|
||||
// Start auth flow
|
||||
http.HandleFunc("/api/auth/indieauth", middleware.RequireUserAccessToken(indieauth.StartAuthFlow)) |
||||
http.HandleFunc("/api/auth/indieauth/callback", indieauth.HandleRedirect) |
||||
http.HandleFunc("/api/auth/provider/indieauth", indieauth.HandleAuthEndpoint) |
||||
|
||||
http.HandleFunc("/api/auth/fediverse", middleware.RequireUserAccessToken(fediverseauth.RegisterFediverseOTPRequest)) |
||||
http.HandleFunc("/api/auth/fediverse/verify", fediverseauth.VerifyFediverseOTPRequest) |
||||
|
||||
// ActivityPub has its own router
|
||||
activitypub.Start(data.GetDatastore()) |
||||
|
||||
// websocket
|
||||
http.HandleFunc("/ws", func(w http.ResponseWriter, r *http.Request) { |
||||
chat.HandleClientConnection(w, r) |
||||
}) |
||||
|
||||
// Optional public static files
|
||||
http.Handle("/public/", http.StripPrefix("/public/", http.FileServer(http.Dir(config.PublicFilesPath)))) |
||||
|
||||
port := config.WebServerPort |
||||
ip := config.WebServerIP |
||||
|
||||
h2s := &http2.Server{} |
||||
|
||||
// Create a custom mux handler to intercept the /debug/vars endpoint.
|
||||
// This is a hack because Prometheus enables this endpoint by default
|
||||
// due to its use of expvar and we do not want this exposed.
|
||||
defaultMux := h2c.NewHandler(http.DefaultServeMux, h2s) |
||||
m := http.NewServeMux() |
||||
|
||||
m.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { |
||||
if r.URL.Path == "/debug/vars" { |
||||
w.WriteHeader(http.StatusNotFound) |
||||
return |
||||
} else if r.URL.Path == "/embed/chat/" || r.URL.Path == "/embed/chat" { |
||||
// Redirect /embed/chat
|
||||
http.Redirect(w, r, "/embed/chat/readonly", http.StatusTemporaryRedirect) |
||||
} else { |
||||
defaultMux.ServeHTTP(w, r) |
||||
} |
||||
}) |
||||
|
||||
compress, _ := httpcompression.DefaultAdapter() // Use the default configuration
|
||||
server := &http.Server{ |
||||
Addr: fmt.Sprintf("%s:%d", ip, port), |
||||
ReadHeaderTimeout: 4 * time.Second, |
||||
Handler: compress(m), |
||||
} |
||||
|
||||
if ip != "0.0.0.0" { |
||||
log.Infof("Web server is listening at %s:%d.", ip, port) |
||||
} else { |
||||
log.Infof("Web server is listening on port %d.", port) |
||||
} |
||||
log.Infoln("Configure this server by visiting /admin.") |
||||
|
||||
return server.ListenAndServe() |
||||
} |
@ -0,0 +1,36 @@
@@ -0,0 +1,36 @@
|
||||
package handlers |
||||
|
||||
import ( |
||||
"encoding/json" |
||||
"net/http" |
||||
|
||||
"github.com/owncast/owncast/core/data" |
||||
"github.com/owncast/owncast/webserver/requests" |
||||
"github.com/owncast/owncast/webserver/responses" |
||||
) |
||||
|
||||
// SetCustomColorVariableValues sets the custom color variables.
|
||||
func (h *Handlers) SetCustomColorVariableValues(w http.ResponseWriter, r *http.Request) { |
||||
if !requests.RequirePOST(w, r) { |
||||
return |
||||
} |
||||
|
||||
type request struct { |
||||
Value map[string]string `json:"value"` |
||||
} |
||||
|
||||
decoder := json.NewDecoder(r.Body) |
||||
var values request |
||||
|
||||
if err := decoder.Decode(&values); err != nil { |
||||
responses.WriteSimpleResponse(w, false, "unable to update appearance variable values") |
||||
return |
||||
} |
||||
|
||||
if err := data.SetCustomColorVariableValues(values.Value); err != nil { |
||||
responses.WriteSimpleResponse(w, false, err.Error()) |
||||
return |
||||
} |
||||
|
||||
responses.WriteSimpleResponse(w, true, "custom appearance variables updated") |
||||
} |
@ -0,0 +1,814 @@
@@ -0,0 +1,814 @@
|
||||
package handlers |
||||
|
||||
import ( |
||||
"encoding/json" |
||||
"fmt" |
||||
"net" |
||||
"net/http" |
||||
"os" |
||||
"path/filepath" |
||||
"strings" |
||||
|
||||
"github.com/owncast/owncast/activitypub/outbox" |
||||
"github.com/owncast/owncast/core/chat" |
||||
"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" |
||||
"github.com/owncast/owncast/webserver/requests" |
||||
"github.com/owncast/owncast/webserver/responses" |
||||
log "github.com/sirupsen/logrus" |
||||
"github.com/teris-io/shortid" |
||||
) |
||||
|
||||
// SetTags will handle the web config request to set tags.
|
||||
func (h *Handlers) SetTags(w http.ResponseWriter, r *http.Request) { |
||||
if !requests.RequirePOST(w, r) { |
||||
return |
||||
} |
||||
|
||||
configValues, success := requests.GetValuesFromRequest(w, r) |
||||
if !success { |
||||
return |
||||
} |
||||
|
||||
tagStrings := make([]string, 0) |
||||
for _, tag := range configValues { |
||||
tagStrings = append(tagStrings, strings.TrimLeft(tag.Value.(string), "#")) |
||||
} |
||||
|
||||
if err := data.SetServerMetadataTags(tagStrings); err != nil { |
||||
responses.WriteSimpleResponse(w, false, err.Error()) |
||||
return |
||||
} |
||||
|
||||
// Update Fediverse followers about this change.
|
||||
if err := outbox.UpdateFollowersWithAccountUpdates(); err != nil { |
||||
responses.WriteSimpleResponse(w, false, err.Error()) |
||||
return |
||||
} |
||||
|
||||
responses.WriteSimpleResponse(w, true, "changed") |
||||
} |
||||
|
||||
// SetStreamTitle will handle the web config request to set the current stream title.
|
||||
func (h *Handlers) SetStreamTitle(w http.ResponseWriter, r *http.Request) { |
||||
if !requests.RequirePOST(w, r) { |
||||
return |
||||
} |
||||
|
||||
configValue, success := requests.GetValueFromRequest(w, r) |
||||
if !success { |
||||
return |
||||
} |
||||
|
||||
value := configValue.Value.(string) |
||||
|
||||
if err := data.SetStreamTitle(value); err != nil { |
||||
responses.WriteSimpleResponse(w, false, err.Error()) |
||||
return |
||||
} |
||||
if value != "" { |
||||
sendSystemChatAction(fmt.Sprintf("Stream title changed to **%s**", value), true) |
||||
go webhooks.SendStreamStatusEvent(models.StreamTitleUpdated) |
||||
} |
||||
responses.WriteSimpleResponse(w, true, "changed") |
||||
} |
||||
|
||||
// ExternalSetStreamTitle will change the stream title on behalf of an external integration API request.
|
||||
func (h *Handlers) ExternalSetStreamTitle(integration user.ExternalAPIUser, w http.ResponseWriter, r *http.Request) { |
||||
h.SetStreamTitle(w, r) |
||||
} |
||||
|
||||
func sendSystemChatAction(messageText string, ephemeral bool) { |
||||
if err := chat.SendSystemAction(messageText, ephemeral); err != nil { |
||||
log.Errorln(err) |
||||
} |
||||
} |
||||
|
||||
// SetServerName will handle the web config request to set the server's name.
|
||||
func (h *Handlers) SetServerName(w http.ResponseWriter, r *http.Request) { |
||||
if !requests.RequirePOST(w, r) { |
||||
return |
||||
} |
||||
|
||||
configValue, success := requests.GetValueFromRequest(w, r) |
||||
if !success { |
||||
return |
||||
} |
||||
|
||||
if err := data.SetServerName(configValue.Value.(string)); err != nil { |
||||
responses.WriteSimpleResponse(w, false, err.Error()) |
||||
return |
||||
} |
||||
|
||||
// Update Fediverse followers about this change.
|
||||
if err := outbox.UpdateFollowersWithAccountUpdates(); err != nil { |
||||
responses.WriteSimpleResponse(w, false, err.Error()) |
||||
return |
||||
} |
||||
|
||||
responses.WriteSimpleResponse(w, true, "changed") |
||||
} |
||||
|
||||
// SetServerSummary will handle the web config request to set the about/summary text.
|
||||
func (h *Handlers) SetServerSummary(w http.ResponseWriter, r *http.Request) { |
||||
if !requests.RequirePOST(w, r) { |
||||
return |
||||
} |
||||
|
||||
configValue, success := requests.GetValueFromRequest(w, r) |
||||
if !success { |
||||
return |
||||
} |
||||
|
||||
if err := data.SetServerSummary(configValue.Value.(string)); err != nil { |
||||
responses.WriteSimpleResponse(w, false, err.Error()) |
||||
return |
||||
} |
||||
|
||||
// Update Fediverse followers about this change.
|
||||
if err := outbox.UpdateFollowersWithAccountUpdates(); err != nil { |
||||
responses.WriteSimpleResponse(w, false, err.Error()) |
||||
return |
||||
} |
||||
|
||||
responses.WriteSimpleResponse(w, true, "changed") |
||||
} |
||||
|
||||
// SetCustomOfflineMessage will set a message to display when the server is offline.
|
||||
func (h *Handlers) SetCustomOfflineMessage(w http.ResponseWriter, r *http.Request) { |
||||
if !requests.RequirePOST(w, r) { |
||||
return |
||||
} |
||||
|
||||
configValue, success := requests.GetValueFromRequest(w, r) |
||||
if !success { |
||||
return |
||||
} |
||||
|
||||
if err := data.SetCustomOfflineMessage(strings.TrimSpace(configValue.Value.(string))); err != nil { |
||||
responses.WriteSimpleResponse(w, false, err.Error()) |
||||
return |
||||
} |
||||
|
||||
responses.WriteSimpleResponse(w, true, "changed") |
||||
} |
||||
|
||||
// SetServerWelcomeMessage will handle the web config request to set the welcome message text.
|
||||
func (h *Handlers) SetServerWelcomeMessage(w http.ResponseWriter, r *http.Request) { |
||||
if !requests.RequirePOST(w, r) { |
||||
return |
||||
} |
||||
|
||||
configValue, success := requests.GetValueFromRequest(w, r) |
||||
if !success { |
||||
return |
||||
} |
||||
|
||||
if err := data.SetServerWelcomeMessage(strings.TrimSpace(configValue.Value.(string))); err != nil { |
||||
responses.WriteSimpleResponse(w, false, err.Error()) |
||||
return |
||||
} |
||||
|
||||
responses.WriteSimpleResponse(w, true, "changed") |
||||
} |
||||
|
||||
// SetExtraPageContent will handle the web config request to set the page markdown content.
|
||||
func (h *Handlers) SetExtraPageContent(w http.ResponseWriter, r *http.Request) { |
||||
if !requests.RequirePOST(w, r) { |
||||
return |
||||
} |
||||
|
||||
configValue, success := requests.GetValueFromRequest(w, r) |
||||
if !success { |
||||
return |
||||
} |
||||
|
||||
if err := data.SetExtraPageBodyContent(configValue.Value.(string)); err != nil { |
||||
responses.WriteSimpleResponse(w, false, err.Error()) |
||||
return |
||||
} |
||||
|
||||
responses.WriteSimpleResponse(w, true, "changed") |
||||
} |
||||
|
||||
// SetAdminPassword will handle the web config request to set the server admin password.
|
||||
func (h *Handlers) SetAdminPassword(w http.ResponseWriter, r *http.Request) { |
||||
if !requests.RequirePOST(w, r) { |
||||
return |
||||
} |
||||
|
||||
configValue, success := requests.GetValueFromRequest(w, r) |
||||
if !success { |
||||
return |
||||
} |
||||
|
||||
if err := data.SetAdminPassword(configValue.Value.(string)); err != nil { |
||||
responses.WriteSimpleResponse(w, false, err.Error()) |
||||
return |
||||
} |
||||
|
||||
responses.WriteSimpleResponse(w, true, "changed") |
||||
} |
||||
|
||||
// SetLogo will handle a new logo image file being uploaded.
|
||||
func (h *Handlers) SetLogo(w http.ResponseWriter, r *http.Request) { |
||||
if !requests.RequirePOST(w, r) { |
||||
return |
||||
} |
||||
|
||||
configValue, success := requests.GetValueFromRequest(w, r) |
||||
if !success { |
||||
return |
||||
} |
||||
|
||||
value, ok := configValue.Value.(string) |
||||
if !ok { |
||||
responses.WriteSimpleResponse(w, false, "unable to find image data") |
||||
return |
||||
} |
||||
bytes, extension, err := utils.DecodeBase64Image(value) |
||||
if err != nil { |
||||
responses.WriteSimpleResponse(w, false, err.Error()) |
||||
return |
||||
} |
||||
|
||||
imgPath := filepath.Join("data", "logo"+extension) |
||||
if err := os.WriteFile(imgPath, bytes, 0o600); err != nil { |
||||
responses.WriteSimpleResponse(w, false, err.Error()) |
||||
return |
||||
} |
||||
|
||||
if err := data.SetLogoPath("logo" + extension); err != nil { |
||||
responses.WriteSimpleResponse(w, false, err.Error()) |
||||
return |
||||
} |
||||
|
||||
if err := data.SetLogoUniquenessString(shortid.MustGenerate()); err != nil { |
||||
log.Error("Error saving logo uniqueness string: ", err) |
||||
} |
||||
|
||||
// Update Fediverse followers about this change.
|
||||
if err := outbox.UpdateFollowersWithAccountUpdates(); err != nil { |
||||
responses.WriteSimpleResponse(w, false, err.Error()) |
||||
return |
||||
} |
||||
|
||||
responses.WriteSimpleResponse(w, true, "changed") |
||||
} |
||||
|
||||
// SetNSFW will handle the web config request to set the NSFW flag.
|
||||
func (h *Handlers) SetNSFW(w http.ResponseWriter, r *http.Request) { |
||||
if !requests.RequirePOST(w, r) { |
||||
return |
||||
} |
||||
|
||||
configValue, success := requests.GetValueFromRequest(w, r) |
||||
if !success { |
||||
return |
||||
} |
||||
|
||||
if err := data.SetNSFW(configValue.Value.(bool)); err != nil { |
||||
responses.WriteSimpleResponse(w, false, err.Error()) |
||||
return |
||||
} |
||||
|
||||
responses.WriteSimpleResponse(w, true, "changed") |
||||
} |
||||
|
||||
// SetFfmpegPath will handle the web config request to validate and set an updated copy of ffmpg.
|
||||
func (h *Handlers) SetFfmpegPath(w http.ResponseWriter, r *http.Request) { |
||||
if !requests.RequirePOST(w, r) { |
||||
return |
||||
} |
||||
|
||||
configValue, success := requests.GetValueFromRequest(w, r) |
||||
if !success { |
||||
return |
||||
} |
||||
|
||||
path := configValue.Value.(string) |
||||
if err := utils.VerifyFFMpegPath(path); err != nil { |
||||
responses.WriteSimpleResponse(w, false, err.Error()) |
||||
return |
||||
} |
||||
|
||||
if err := data.SetFfmpegPath(configValue.Value.(string)); err != nil { |
||||
responses.WriteSimpleResponse(w, false, err.Error()) |
||||
return |
||||
} |
||||
|
||||
responses.WriteSimpleResponse(w, true, "changed") |
||||
} |
||||
|
||||
// SetWebServerPort will handle the web config request to set the server's HTTP port.
|
||||
func (h *Handlers) SetWebServerPort(w http.ResponseWriter, r *http.Request) { |
||||
if !requests.RequirePOST(w, r) { |
||||
return |
||||
} |
||||
|
||||
configValue, success := requests.GetValueFromRequest(w, r) |
||||
if !success { |
||||
return |
||||
} |
||||
|
||||
if port, ok := configValue.Value.(float64); ok { |
||||
if (port < 1) || (port > 65535) { |
||||
responses.WriteSimpleResponse(w, false, "Port number must be between 1 and 65535") |
||||
return |
||||
} |
||||
if err := data.SetHTTPPortNumber(port); err != nil { |
||||
responses.WriteSimpleResponse(w, false, err.Error()) |
||||
return |
||||
} |
||||
|
||||
responses.WriteSimpleResponse(w, true, "HTTP port set") |
||||
return |
||||
} |
||||
|
||||
responses.WriteSimpleResponse(w, false, "Invalid type or value, port must be a number") |
||||
} |
||||
|
||||
// SetWebServerIP will handle the web config request to set the server's HTTP listen address.
|
||||
func (h *Handlers) SetWebServerIP(w http.ResponseWriter, r *http.Request) { |
||||
if !requests.RequirePOST(w, r) { |
||||
return |
||||
} |
||||
|
||||
configValue, success := requests.GetValueFromRequest(w, r) |
||||
if !success { |
||||
return |
||||
} |
||||
|
||||
if input, ok := configValue.Value.(string); ok { |
||||
if ip := net.ParseIP(input); ip != nil { |
||||
if err := data.SetHTTPListenAddress(ip.String()); err != nil { |
||||
responses.WriteSimpleResponse(w, false, err.Error()) |
||||
return |
||||
} |
||||
|
||||
responses.WriteSimpleResponse(w, true, "HTTP listen address set") |
||||
return |
||||
} |
||||
|
||||
responses.WriteSimpleResponse(w, false, "Invalid IP address") |
||||
return |
||||
} |
||||
responses.WriteSimpleResponse(w, false, "Invalid type or value, IP address must be a string") |
||||
} |
||||
|
||||
// SetRTMPServerPort will handle the web config request to set the inbound RTMP port.
|
||||
func (h *Handlers) SetRTMPServerPort(w http.ResponseWriter, r *http.Request) { |
||||
if !requests.RequirePOST(w, r) { |
||||
return |
||||
} |
||||
|
||||
configValue, success := requests.GetValueFromRequest(w, r) |
||||
if !success { |
||||
return |
||||
} |
||||
|
||||
if err := data.SetRTMPPortNumber(configValue.Value.(float64)); err != nil { |
||||
responses.WriteSimpleResponse(w, false, err.Error()) |
||||
return |
||||
} |
||||
|
||||
responses.WriteSimpleResponse(w, true, "rtmp port set") |
||||
} |
||||
|
||||
// SetServerURL will handle the web config request to set the full server URL.
|
||||
func (h *Handlers) SetServerURL(w http.ResponseWriter, r *http.Request) { |
||||
if !requests.RequirePOST(w, r) { |
||||
return |
||||
} |
||||
|
||||
configValue, success := requests.GetValueFromRequest(w, r) |
||||
if !success { |
||||
return |
||||
} |
||||
|
||||
rawValue, ok := configValue.Value.(string) |
||||
if !ok { |
||||
responses.WriteSimpleResponse(w, false, "could not read server url") |
||||
return |
||||
} |
||||
|
||||
serverHostString := utils.GetHostnameFromURLString(rawValue) |
||||
if serverHostString == "" { |
||||
responses.WriteSimpleResponse(w, false, "server url value invalid") |
||||
return |
||||
} |
||||
|
||||
// Trim any trailing slash
|
||||
serverURL := strings.TrimRight(rawValue, "/") |
||||
|
||||
if err := data.SetServerURL(serverURL); err != nil { |
||||
responses.WriteSimpleResponse(w, false, err.Error()) |
||||
return |
||||
} |
||||
|
||||
responses.WriteSimpleResponse(w, true, "server url set") |
||||
} |
||||
|
||||
// SetSocketHostOverride will set the host override for the websocket.
|
||||
func (h *Handlers) SetSocketHostOverride(w http.ResponseWriter, r *http.Request) { |
||||
if !requests.RequirePOST(w, r) { |
||||
return |
||||
} |
||||
|
||||
configValue, success := requests.GetValueFromRequest(w, r) |
||||
if !success { |
||||
return |
||||
} |
||||
|
||||
if err := data.SetWebsocketOverrideHost(configValue.Value.(string)); err != nil { |
||||
responses.WriteSimpleResponse(w, false, err.Error()) |
||||
return |
||||
} |
||||
|
||||
responses.WriteSimpleResponse(w, true, "websocket host override set") |
||||
} |
||||
|
||||
// SetDirectoryEnabled will handle the web config request to enable or disable directory registration.
|
||||
func (h *Handlers) SetDirectoryEnabled(w http.ResponseWriter, r *http.Request) { |
||||
if !requests.RequirePOST(w, r) { |
||||
return |
||||
} |
||||
|
||||
configValue, success := requests.GetValueFromRequest(w, r) |
||||
if !success { |
||||
return |
||||
} |
||||
|
||||
if err := data.SetDirectoryEnabled(configValue.Value.(bool)); err != nil { |
||||
responses.WriteSimpleResponse(w, false, err.Error()) |
||||
return |
||||
} |
||||
responses.WriteSimpleResponse(w, true, "directory state changed") |
||||
} |
||||
|
||||
// SetStreamLatencyLevel will handle the web config request to set the stream latency level.
|
||||
func (h *Handlers) SetStreamLatencyLevel(w http.ResponseWriter, r *http.Request) { |
||||
if !requests.RequirePOST(w, r) { |
||||
return |
||||
} |
||||
|
||||
configValue, success := requests.GetValueFromRequest(w, r) |
||||
if !success { |
||||
return |
||||
} |
||||
|
||||
if err := data.SetStreamLatencyLevel(configValue.Value.(float64)); err != nil { |
||||
responses.WriteSimpleResponse(w, false, "error setting stream latency "+err.Error()) |
||||
return |
||||
} |
||||
|
||||
responses.WriteSimpleResponse(w, true, "set stream latency") |
||||
} |
||||
|
||||
// SetS3Configuration will handle the web config request to set the storage configuration.
|
||||
func (h *Handlers) SetS3Configuration(w http.ResponseWriter, r *http.Request) { |
||||
if !requests.RequirePOST(w, r) { |
||||
return |
||||
} |
||||
|
||||
type s3ConfigurationRequest struct { |
||||
Value models.S3 `json:"value"` |
||||
} |
||||
|
||||
decoder := json.NewDecoder(r.Body) |
||||
var newS3Config s3ConfigurationRequest |
||||
if err := decoder.Decode(&newS3Config); err != nil { |
||||
responses.WriteSimpleResponse(w, false, "unable to update s3 config with provided values") |
||||
return |
||||
} |
||||
|
||||
if newS3Config.Value.Enabled { |
||||
if newS3Config.Value.Endpoint == "" || !utils.IsValidURL((newS3Config.Value.Endpoint)) { |
||||
responses.WriteSimpleResponse(w, false, "s3 support requires an endpoint") |
||||
return |
||||
} |
||||
|
||||
if newS3Config.Value.AccessKey == "" || newS3Config.Value.Secret == "" { |
||||
responses.WriteSimpleResponse(w, false, "s3 support requires an access key and secret") |
||||
return |
||||
} |
||||
|
||||
if newS3Config.Value.Region == "" { |
||||
responses.WriteSimpleResponse(w, false, "s3 support requires a region and endpoint") |
||||
return |
||||
} |
||||
|
||||
if newS3Config.Value.Bucket == "" { |
||||
responses.WriteSimpleResponse(w, false, "s3 support requires a bucket created for storing public video segments") |
||||
return |
||||
} |
||||
} |
||||
|
||||
if err := data.SetS3Config(newS3Config.Value); err != nil { |
||||
responses.WriteSimpleResponse(w, false, err.Error()) |
||||
return |
||||
} |
||||
responses.WriteSimpleResponse(w, true, "storage configuration changed") |
||||
} |
||||
|
||||
// SetStreamOutputVariants will handle the web config request to set the video output stream variants.
|
||||
func (h *Handlers) SetStreamOutputVariants(w http.ResponseWriter, r *http.Request) { |
||||
if !requests.RequirePOST(w, r) { |
||||
return |
||||
} |
||||
|
||||
type streamOutputVariantRequest struct { |
||||
Value []models.StreamOutputVariant `json:"value"` |
||||
} |
||||
|
||||
decoder := json.NewDecoder(r.Body) |
||||
var videoVariants streamOutputVariantRequest |
||||
if err := decoder.Decode(&videoVariants); err != nil { |
||||
responses.WriteSimpleResponse(w, false, "unable to update video config with provided values "+err.Error()) |
||||
return |
||||
} |
||||
|
||||
if err := data.SetStreamOutputVariants(videoVariants.Value); err != nil { |
||||
responses.WriteSimpleResponse(w, false, "unable to update video config with provided values "+err.Error()) |
||||
return |
||||
} |
||||
|
||||
responses.WriteSimpleResponse(w, true, "stream output variants updated") |
||||
} |
||||
|
||||
// SetSocialHandles will handle the web config request to set the external social profile links.
|
||||
func (h *Handlers) SetSocialHandles(w http.ResponseWriter, r *http.Request) { |
||||
if !requests.RequirePOST(w, r) { |
||||
return |
||||
} |
||||
|
||||
type socialHandlesRequest struct { |
||||
Value []models.SocialHandle `json:"value"` |
||||
} |
||||
|
||||
decoder := json.NewDecoder(r.Body) |
||||
var socialHandles socialHandlesRequest |
||||
if err := decoder.Decode(&socialHandles); err != nil { |
||||
responses.WriteSimpleResponse(w, false, "unable to update social handles with provided values") |
||||
return |
||||
} |
||||
|
||||
if err := data.SetSocialHandles(socialHandles.Value); err != nil { |
||||
responses.WriteSimpleResponse(w, false, "unable to update social handles with provided values") |
||||
return |
||||
} |
||||
|
||||
// Update Fediverse followers about this change.
|
||||
if err := outbox.UpdateFollowersWithAccountUpdates(); err != nil { |
||||
responses.WriteSimpleResponse(w, false, err.Error()) |
||||
return |
||||
} |
||||
|
||||
responses.WriteSimpleResponse(w, true, "social handles updated") |
||||
} |
||||
|
||||
// SetChatDisabled will disable chat functionality.
|
||||
func (h *Handlers) SetChatDisabled(w http.ResponseWriter, r *http.Request) { |
||||
if !requests.RequirePOST(w, r) { |
||||
return |
||||
} |
||||
|
||||
configValue, success := requests.GetValueFromRequest(w, r) |
||||
if !success { |
||||
responses.WriteSimpleResponse(w, false, "unable to update chat disabled") |
||||
return |
||||
} |
||||
|
||||
if err := data.SetChatDisabled(configValue.Value.(bool)); err != nil { |
||||
responses.WriteSimpleResponse(w, false, err.Error()) |
||||
return |
||||
} |
||||
|
||||
responses.WriteSimpleResponse(w, true, "chat disabled status updated") |
||||
} |
||||
|
||||
// SetVideoCodec will change the codec used for video encoding.
|
||||
func (h *Handlers) SetVideoCodec(w http.ResponseWriter, r *http.Request) { |
||||
if !requests.RequirePOST(w, r) { |
||||
return |
||||
} |
||||
|
||||
configValue, success := requests.GetValueFromRequest(w, r) |
||||
if !success { |
||||
responses.WriteSimpleResponse(w, false, "unable to change video codec") |
||||
return |
||||
} |
||||
|
||||
if err := data.SetVideoCodec(configValue.Value.(string)); err != nil { |
||||
responses.WriteSimpleResponse(w, false, "unable to update codec") |
||||
return |
||||
} |
||||
|
||||
responses.WriteSimpleResponse(w, true, "video codec updated") |
||||
} |
||||
|
||||
// SetExternalActions will set the 3rd party actions for the web interface.
|
||||
func (h *Handlers) SetExternalActions(w http.ResponseWriter, r *http.Request) { |
||||
type externalActionsRequest struct { |
||||
Value []models.ExternalAction `json:"value"` |
||||
} |
||||
|
||||
decoder := json.NewDecoder(r.Body) |
||||
var actions externalActionsRequest |
||||
if err := decoder.Decode(&actions); err != nil { |
||||
responses.WriteSimpleResponse(w, false, "unable to update external actions with provided values") |
||||
return |
||||
} |
||||
|
||||
if err := data.SetExternalActions(actions.Value); err != nil { |
||||
responses.WriteSimpleResponse(w, false, "unable to update external actions with provided values") |
||||
return |
||||
} |
||||
|
||||
responses.WriteSimpleResponse(w, true, "external actions update") |
||||
} |
||||
|
||||
// SetCustomStyles will set the CSS string we insert into the page.
|
||||
func (h *Handlers) SetCustomStyles(w http.ResponseWriter, r *http.Request) { |
||||
customStyles, success := requests.GetValueFromRequest(w, r) |
||||
if !success { |
||||
responses.WriteSimpleResponse(w, false, "unable to update custom styles") |
||||
return |
||||
} |
||||
|
||||
if err := data.SetCustomStyles(customStyles.Value.(string)); err != nil { |
||||
responses.WriteSimpleResponse(w, false, err.Error()) |
||||
return |
||||
} |
||||
|
||||
responses.WriteSimpleResponse(w, true, "custom styles updated") |
||||
} |
||||
|
||||
// SetCustomJavascript will set the Javascript string we insert into the page.
|
||||
func (h *Handlers) SetCustomJavascript(w http.ResponseWriter, r *http.Request) { |
||||
customJavascript, success := requests.GetValueFromRequest(w, r) |
||||
if !success { |
||||
responses.WriteSimpleResponse(w, false, "unable to update custom javascript") |
||||
return |
||||
} |
||||
|
||||
if err := data.SetCustomJavascript(customJavascript.Value.(string)); err != nil { |
||||
responses.WriteSimpleResponse(w, false, err.Error()) |
||||
return |
||||
} |
||||
|
||||
responses.WriteSimpleResponse(w, true, "custom styles updated") |
||||
} |
||||
|
||||
// SetForbiddenUsernameList will set the list of usernames we do not allow to use.
|
||||
func (h *Handlers) 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 { |
||||
responses.WriteSimpleResponse(w, false, "unable to update forbidden usernames with provided values") |
||||
return |
||||
} |
||||
|
||||
if err := data.SetForbiddenUsernameList(request.Value); err != nil { |
||||
responses.WriteSimpleResponse(w, false, err.Error()) |
||||
return |
||||
} |
||||
|
||||
responses.WriteSimpleResponse(w, true, "forbidden username list updated") |
||||
} |
||||
|
||||
// SetSuggestedUsernameList will set the list of suggested usernames that newly registered users are assigned if it isn't inferred otherwise (i.e. through a proxy).
|
||||
func (h *Handlers) SetSuggestedUsernameList(w http.ResponseWriter, r *http.Request) { |
||||
type suggestedUsernameListRequest struct { |
||||
Value []string `json:"value"` |
||||
} |
||||
|
||||
decoder := json.NewDecoder(r.Body) |
||||
var request suggestedUsernameListRequest |
||||
|
||||
if err := decoder.Decode(&request); err != nil { |
||||
responses.WriteSimpleResponse(w, false, "unable to update suggested usernames with provided values") |
||||
return |
||||
} |
||||
|
||||
if err := data.SetSuggestedUsernamesList(request.Value); err != nil { |
||||
responses.WriteSimpleResponse(w, false, err.Error()) |
||||
return |
||||
} |
||||
|
||||
responses.WriteSimpleResponse(w, true, "suggested username list updated") |
||||
} |
||||
|
||||
// SetChatJoinMessagesEnabled will enable or disable the chat join messages.
|
||||
func (h *Handlers) SetChatJoinMessagesEnabled(w http.ResponseWriter, r *http.Request) { |
||||
if !requests.RequirePOST(w, r) { |
||||
return |
||||
} |
||||
|
||||
configValue, success := requests.GetValueFromRequest(w, r) |
||||
if !success { |
||||
responses.WriteSimpleResponse(w, false, "unable to update chat join messages enabled") |
||||
return |
||||
} |
||||
|
||||
if err := data.SetChatJoinMessagesEnabled(configValue.Value.(bool)); err != nil { |
||||
responses.WriteSimpleResponse(w, false, err.Error()) |
||||
return |
||||
} |
||||
|
||||
responses.WriteSimpleResponse(w, true, "chat join message status updated") |
||||
} |
||||
|
||||
// SetHideViewerCount will enable or disable hiding the viewer count.
|
||||
func (h *Handlers) SetHideViewerCount(w http.ResponseWriter, r *http.Request) { |
||||
if !requests.RequirePOST(w, r) { |
||||
return |
||||
} |
||||
|
||||
configValue, success := requests.GetValueFromRequest(w, r) |
||||
if !success { |
||||
responses.WriteSimpleResponse(w, false, "unable to update hiding viewer count") |
||||
return |
||||
} |
||||
|
||||
if err := data.SetHideViewerCount(configValue.Value.(bool)); err != nil { |
||||
responses.WriteSimpleResponse(w, false, err.Error()) |
||||
return |
||||
} |
||||
|
||||
responses.WriteSimpleResponse(w, true, "hide viewer count setting updated") |
||||
} |
||||
|
||||
// SetDisableSearchIndexing will set search indexing support.
|
||||
func (h *Handlers) SetDisableSearchIndexing(w http.ResponseWriter, r *http.Request) { |
||||
if !requests.RequirePOST(w, r) { |
||||
return |
||||
} |
||||
|
||||
configValue, success := requests.GetValueFromRequest(w, r) |
||||
if !success { |
||||
responses.WriteSimpleResponse(w, false, "unable to update search indexing") |
||||
return |
||||
} |
||||
|
||||
if err := data.SetDisableSearchIndexing(configValue.Value.(bool)); err != nil { |
||||
responses.WriteSimpleResponse(w, false, err.Error()) |
||||
return |
||||
} |
||||
|
||||
responses.WriteSimpleResponse(w, true, "search indexing support updated") |
||||
} |
||||
|
||||
// SetVideoServingEndpoint will save the video serving endpoint.
|
||||
func (h *Handlers) SetVideoServingEndpoint(w http.ResponseWriter, r *http.Request) { |
||||
endpoint, success := requests.GetValueFromRequest(w, r) |
||||
if !success { |
||||
responses.WriteSimpleResponse(w, false, "unable to update custom video serving endpoint") |
||||
return |
||||
} |
||||
|
||||
value, ok := endpoint.Value.(string) |
||||
if !ok { |
||||
responses.WriteSimpleResponse(w, false, "unable to update custom video serving endpoint") |
||||
return |
||||
} |
||||
|
||||
if err := data.SetVideoServingEndpoint(value); err != nil { |
||||
responses.WriteSimpleResponse(w, false, err.Error()) |
||||
return |
||||
} |
||||
|
||||
responses.WriteSimpleResponse(w, true, "custom video serving endpoint updated") |
||||
} |
||||
|
||||
// SetStreamKeys will set the valid stream keys.
|
||||
func (h *Handlers) SetStreamKeys(w http.ResponseWriter, r *http.Request) { |
||||
if !requests.RequirePOST(w, r) { |
||||
return |
||||
} |
||||
|
||||
type streamKeysRequest struct { |
||||
Value []models.StreamKey `json:"value"` |
||||
} |
||||
|
||||
decoder := json.NewDecoder(r.Body) |
||||
var streamKeys streamKeysRequest |
||||
if err := decoder.Decode(&streamKeys); err != nil { |
||||
responses.WriteSimpleResponse(w, false, "unable to update stream keys with provided values") |
||||
return |
||||
} |
||||
|
||||
if err := data.SetStreamKeys(streamKeys.Value); err != nil { |
||||
responses.WriteSimpleResponse(w, false, err.Error()) |
||||
return |
||||
} |
||||
|
||||
responses.WriteSimpleResponse(w, true, "changed") |
||||
} |
@ -1,25 +1,25 @@
@@ -1,25 +1,25 @@
|
||||
package admin |
||||
package handlers |
||||
|
||||
import ( |
||||
"encoding/json" |
||||
"net/http" |
||||
|
||||
"github.com/owncast/owncast/controllers" |
||||
"github.com/owncast/owncast/core/chat" |
||||
"github.com/owncast/owncast/core/user" |
||||
"github.com/owncast/owncast/webserver/responses" |
||||
) |
||||
|
||||
// GetConnectedChatClients returns currently connected clients.
|
||||
func GetConnectedChatClients(w http.ResponseWriter, r *http.Request) { |
||||
func (h *Handlers) GetConnectedChatClients(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) |
||||
responses.InternalErrorHandler(w, err) |
||||
} |
||||
} |
||||
|
||||
// ExternalGetConnectedChatClients returns currently connected clients.
|
||||
func ExternalGetConnectedChatClients(integration user.ExternalAPIUser, w http.ResponseWriter, r *http.Request) { |
||||
GetConnectedChatClients(w, r) |
||||
func (h *Handlers) ExternalGetConnectedChatClients(integration user.ExternalAPIUser, w http.ResponseWriter, r *http.Request) { |
||||
h.GetConnectedChatClients(w, r) |
||||
} |
@ -1,20 +1,20 @@
@@ -1,20 +1,20 @@
|
||||
package admin |
||||
package handlers |
||||
|
||||
import ( |
||||
"net/http" |
||||
|
||||
"github.com/owncast/owncast/controllers" |
||||
"github.com/owncast/owncast/core/data" |
||||
"github.com/owncast/owncast/webserver/responses" |
||||
log "github.com/sirupsen/logrus" |
||||
) |
||||
|
||||
// ResetYPRegistration will clear the YP protocol registration key.
|
||||
func ResetYPRegistration(w http.ResponseWriter, r *http.Request) { |
||||
func (h *Handlers) ResetYPRegistration(w http.ResponseWriter, r *http.Request) { |
||||
log.Traceln("Resetting YP registration key") |
||||
if err := data.SetDirectoryRegistrationKey(""); err != nil { |
||||
log.Errorln(err) |
||||
controllers.WriteSimpleResponse(w, false, err.Error()) |
||||
responses.WriteSimpleResponse(w, false, err.Error()) |
||||
return |
||||
} |
||||
controllers.WriteSimpleResponse(w, true, "reset") |
||||
responses.WriteSimpleResponse(w, true, "reset") |
||||
} |
@ -0,0 +1,180 @@
@@ -0,0 +1,180 @@
|
||||
package handlers |
||||
|
||||
import ( |
||||
"net/http" |
||||
|
||||
"github.com/owncast/owncast/activitypub" |
||||
"github.com/owncast/owncast/activitypub/outbox" |
||||
"github.com/owncast/owncast/activitypub/persistence" |
||||
"github.com/owncast/owncast/core/data" |
||||
"github.com/owncast/owncast/webserver/requests" |
||||
"github.com/owncast/owncast/webserver/responses" |
||||
) |
||||
|
||||
// SendFederatedMessage will send a manual message to the fediverse.
|
||||
func (h *Handlers) SendFederatedMessage(w http.ResponseWriter, r *http.Request) { |
||||
if !requests.RequirePOST(w, r) { |
||||
return |
||||
} |
||||
|
||||
configValue, success := requests.GetValueFromRequest(w, r) |
||||
if !success { |
||||
return |
||||
} |
||||
|
||||
message, ok := configValue.Value.(string) |
||||
if !ok { |
||||
responses.WriteSimpleResponse(w, false, "unable to send message") |
||||
return |
||||
} |
||||
|
||||
if err := activitypub.SendPublicFederatedMessage(message); err != nil { |
||||
responses.WriteSimpleResponse(w, false, err.Error()) |
||||
return |
||||
} |
||||
|
||||
responses.WriteSimpleResponse(w, true, "sent") |
||||
} |
||||
|
||||
// SetFederationEnabled will set if Federation features are enabled.
|
||||
func (h *Handlers) SetFederationEnabled(w http.ResponseWriter, r *http.Request) { |
||||
if !requests.RequirePOST(w, r) { |
||||
return |
||||
} |
||||
|
||||
configValue, success := requests.GetValueFromRequest(w, r) |
||||
if !success { |
||||
return |
||||
} |
||||
|
||||
if err := data.SetFederationEnabled(configValue.Value.(bool)); err != nil { |
||||
responses.WriteSimpleResponse(w, false, err.Error()) |
||||
return |
||||
} |
||||
responses.WriteSimpleResponse(w, true, "federation features saved") |
||||
} |
||||
|
||||
// SetFederationActivityPrivate will set if Federation features are private to followers.
|
||||
func (h *Handlers) SetFederationActivityPrivate(w http.ResponseWriter, r *http.Request) { |
||||
if !requests.RequirePOST(w, r) { |
||||
return |
||||
} |
||||
|
||||
configValue, success := requests.GetValueFromRequest(w, r) |
||||
if !success { |
||||
return |
||||
} |
||||
|
||||
if err := data.SetFederationIsPrivate(configValue.Value.(bool)); err != nil { |
||||
responses.WriteSimpleResponse(w, false, err.Error()) |
||||
return |
||||
} |
||||
|
||||
// Update Fediverse followers about this change.
|
||||
if err := outbox.UpdateFollowersWithAccountUpdates(); err != nil { |
||||
responses.WriteSimpleResponse(w, false, err.Error()) |
||||
return |
||||
} |
||||
|
||||
responses.WriteSimpleResponse(w, true, "federation private saved") |
||||
} |
||||
|
||||
// SetFederationShowEngagement will set if Fedivese engagement shows in chat.
|
||||
func (h *Handlers) SetFederationShowEngagement(w http.ResponseWriter, r *http.Request) { |
||||
if !requests.RequirePOST(w, r) { |
||||
return |
||||
} |
||||
|
||||
configValue, success := requests.GetValueFromRequest(w, r) |
||||
if !success { |
||||
return |
||||
} |
||||
|
||||
if err := data.SetFederationShowEngagement(configValue.Value.(bool)); err != nil { |
||||
responses.WriteSimpleResponse(w, false, err.Error()) |
||||
return |
||||
} |
||||
responses.WriteSimpleResponse(w, true, "federation show engagement saved") |
||||
} |
||||
|
||||
// SetFederationUsername will set the local actor username used for federation activities.
|
||||
func (h *Handlers) SetFederationUsername(w http.ResponseWriter, r *http.Request) { |
||||
if !requests.RequirePOST(w, r) { |
||||
return |
||||
} |
||||
|
||||
configValue, success := requests.GetValueFromRequest(w, r) |
||||
if !success { |
||||
return |
||||
} |
||||
|
||||
if err := data.SetFederationUsername(configValue.Value.(string)); err != nil { |
||||
responses.WriteSimpleResponse(w, false, err.Error()) |
||||
return |
||||
} |
||||
|
||||
responses.WriteSimpleResponse(w, true, "username saved") |
||||
} |
||||
|
||||
// SetFederationGoLiveMessage will set the federated message sent when the streamer goes live.
|
||||
func (h *Handlers) SetFederationGoLiveMessage(w http.ResponseWriter, r *http.Request) { |
||||
if !requests.RequirePOST(w, r) { |
||||
return |
||||
} |
||||
|
||||
configValue, success := requests.GetValueFromRequest(w, r) |
||||
if !success { |
||||
return |
||||
} |
||||
|
||||
if err := data.SetFederationGoLiveMessage(configValue.Value.(string)); err != nil { |
||||
responses.WriteSimpleResponse(w, false, err.Error()) |
||||
return |
||||
} |
||||
|
||||
responses.WriteSimpleResponse(w, true, "message saved") |
||||
} |
||||
|
||||
// SetFederationBlockDomains saves a list of domains to block on the Fediverse.
|
||||
func (h *Handlers) SetFederationBlockDomains(w http.ResponseWriter, r *http.Request) { |
||||
if !requests.RequirePOST(w, r) { |
||||
return |
||||
} |
||||
|
||||
configValues, success := requests.GetValuesFromRequest(w, r) |
||||
if !success { |
||||
responses.WriteSimpleResponse(w, false, "unable to handle provided domains") |
||||
return |
||||
} |
||||
|
||||
domainStrings := make([]string, 0) |
||||
for _, domain := range configValues { |
||||
domainStrings = append(domainStrings, domain.Value.(string)) |
||||
} |
||||
|
||||
if err := data.SetBlockedFederatedDomains(domainStrings); err != nil { |
||||
responses.WriteSimpleResponse(w, false, err.Error()) |
||||
return |
||||
} |
||||
|
||||
responses.WriteSimpleResponse(w, true, "saved") |
||||
} |
||||
|
||||
// GetFederatedActions will return the saved list of accepted inbound
|
||||
// federated activities.
|
||||
func (h *Handlers) GetFederatedActions(page int, pageSize int, w http.ResponseWriter, r *http.Request) { |
||||
offset := pageSize * page |
||||
|
||||
activities, total, err := persistence.GetInboundActivities(pageSize, offset) |
||||
if err != nil { |
||||
responses.WriteSimpleResponse(w, false, err.Error()) |
||||
return |
||||
} |
||||
|
||||
response := responses.PaginatedResponse{ |
||||
Total: total, |
||||
Results: activities, |
||||
} |
||||
|
||||
responses.WriteResponse(w, response) |
||||
} |
@ -0,0 +1,61 @@
@@ -0,0 +1,61 @@
|
||||
package handlers |
||||
|
||||
import ( |
||||
"encoding/json" |
||||
"net/http" |
||||
|
||||
"github.com/owncast/owncast/core/data" |
||||
"github.com/owncast/owncast/models" |
||||
"github.com/owncast/owncast/webserver/requests" |
||||
"github.com/owncast/owncast/webserver/responses" |
||||
) |
||||
|
||||
// SetDiscordNotificationConfiguration will set the discord notification configuration.
|
||||
func (h *Handlers) SetDiscordNotificationConfiguration(w http.ResponseWriter, r *http.Request) { |
||||
if !requests.RequirePOST(w, r) { |
||||
return |
||||
} |
||||
|
||||
type request struct { |
||||
Value models.DiscordConfiguration `json:"value"` |
||||
} |
||||
|
||||
decoder := json.NewDecoder(r.Body) |
||||
var config request |
||||
if err := decoder.Decode(&config); err != nil { |
||||
responses.WriteSimpleResponse(w, false, "unable to update discord config with provided values") |
||||
return |
||||
} |
||||
|
||||
if err := data.SetDiscordConfig(config.Value); err != nil { |
||||
responses.WriteSimpleResponse(w, false, "unable to update discord config with provided values") |
||||
return |
||||
} |
||||
|
||||
responses.WriteSimpleResponse(w, true, "updated discord config with provided values") |
||||
} |
||||
|
||||
// SetBrowserNotificationConfiguration will set the browser notification configuration.
|
||||
func (h *Handlers) SetBrowserNotificationConfiguration(w http.ResponseWriter, r *http.Request) { |
||||
if !requests.RequirePOST(w, r) { |
||||
return |
||||
} |
||||
|
||||
type request struct { |
||||
Value models.BrowserNotificationConfiguration `json:"value"` |
||||
} |
||||
|
||||
decoder := json.NewDecoder(r.Body) |
||||
var config request |
||||
if err := decoder.Decode(&config); err != nil { |
||||
responses.WriteSimpleResponse(w, false, "unable to update browser push config with provided values") |
||||
return |
||||
} |
||||
|
||||
if err := data.SetBrowserPushConfig(config.Value); err != nil { |
||||
responses.WriteSimpleResponse(w, false, "unable to update browser push config with provided values") |
||||
return |
||||
} |
||||
|
||||
responses.WriteSimpleResponse(w, true, "updated browser push config with provided values") |
||||
} |
@ -0,0 +1,21 @@
@@ -0,0 +1,21 @@
|
||||
package handlers |
||||
|
||||
import ( |
||||
"net/http" |
||||
|
||||
"github.com/owncast/owncast/core" |
||||
"github.com/owncast/owncast/webserver/responses" |
||||
|
||||
"github.com/owncast/owncast/core/rtmp" |
||||
) |
||||
|
||||
// DisconnectInboundConnection will force-disconnect an inbound stream.
|
||||
func (h *Handlers) DisconnectInboundConnection(w http.ResponseWriter, r *http.Request) { |
||||
if !core.GetStatus().Online { |
||||
responses.WriteSimpleResponse(w, false, "no inbound stream connected") |
||||
return |
||||
} |
||||
|
||||
rtmp.Disconnect() |
||||
responses.WriteSimpleResponse(w, true, "inbound stream disconnected") |
||||
} |
@ -1,22 +1,23 @@
@@ -1,22 +1,23 @@
|
||||
package controllers |
||||
package handlers |
||||
|
||||
import ( |
||||
"net/http" |
||||
|
||||
"github.com/owncast/owncast/activitypub/persistence" |
||||
"github.com/owncast/owncast/webserver/responses" |
||||
) |
||||
|
||||
// GetFollowers will handle an API request to fetch the list of followers (non-activitypub response).
|
||||
func GetFollowers(offset int, limit int, w http.ResponseWriter, r *http.Request) { |
||||
func (h *Handlers) GetFollowers(offset int, limit int, w http.ResponseWriter, r *http.Request) { |
||||
followers, total, err := persistence.GetFederationFollowers(limit, offset) |
||||
if err != nil { |
||||
WriteSimpleResponse(w, false, "unable to fetch followers") |
||||
responses.WriteSimpleResponse(w, false, "unable to fetch followers") |
||||
return |
||||
} |
||||
|
||||
response := PaginatedResponse{ |
||||
response := responses.PaginatedResponse{ |
||||
Total: total, |
||||
Results: followers, |
||||
} |
||||
WriteResponse(w, response) |
||||
responses.WriteResponse(w, response) |
||||
} |
@ -0,0 +1,8 @@
@@ -0,0 +1,8 @@
|
||||
package handlers |
||||
|
||||
type Handlers struct{} |
||||
|
||||
// New creates a new instances of web server handlers.
|
||||
func New() *Handlers { |
||||
return &Handlers{} |
||||
} |
@ -1,4 +1,4 @@
@@ -1,4 +1,4 @@
|
||||
package controllers |
||||
package handlers |
||||
|
||||
import ( |
||||
"net/http" |
@ -0,0 +1,6 @@
@@ -0,0 +1,6 @@
|
||||
package requests |
||||
|
||||
// ConfigValue is a container object that holds a value, is encoded, and saved to the database.
|
||||
type ConfigValue struct { |
||||
Value interface{} `json:"value"` |
||||
} |
@ -0,0 +1,50 @@
@@ -0,0 +1,50 @@
|
||||
package requests |
||||
|
||||
import ( |
||||
"encoding/json" |
||||
"net/http" |
||||
"reflect" |
||||
|
||||
"github.com/owncast/owncast/webserver/responses" |
||||
log "github.com/sirupsen/logrus" |
||||
) |
||||
|
||||
func RequirePOST(w http.ResponseWriter, r *http.Request) bool { |
||||
if r.Method != http.MethodPost { |
||||
responses.WriteSimpleResponse(w, false, r.Method+" not supported") |
||||
return false |
||||
} |
||||
|
||||
return true |
||||
} |
||||
|
||||
func GetValueFromRequest(w http.ResponseWriter, r *http.Request) (ConfigValue, bool) { |
||||
decoder := json.NewDecoder(r.Body) |
||||
var configValue ConfigValue |
||||
if err := decoder.Decode(&configValue); err != nil { |
||||
log.Warnln(err) |
||||
responses.WriteSimpleResponse(w, false, "unable to parse new value") |
||||
return configValue, false |
||||
} |
||||
|
||||
return configValue, true |
||||
} |
||||
|
||||
func GetValuesFromRequest(w http.ResponseWriter, r *http.Request) ([]ConfigValue, bool) { |
||||
var values []ConfigValue |
||||
|
||||
decoder := json.NewDecoder(r.Body) |
||||
var configValue ConfigValue |
||||
if err := decoder.Decode(&configValue); err != nil { |
||||
responses.WriteSimpleResponse(w, false, "unable to parse array of values") |
||||
return values, false |
||||
} |
||||
|
||||
object := reflect.ValueOf(configValue.Value) |
||||
|
||||
for i := 0; i < object.Len(); i++ { |
||||
values = append(values, ConfigValue{Value: object.Index(i).Interface()}) |
||||
} |
||||
|
||||
return values, true |
||||
} |
@ -0,0 +1,17 @@
@@ -0,0 +1,17 @@
|
||||
package responses |
||||
|
||||
import ( |
||||
"log" |
||||
"net/http" |
||||
"strconv" |
||||
) |
||||
|
||||
func WriteBytesAsImage(data []byte, contentType string, w http.ResponseWriter, cacheSeconds int) { |
||||
w.Header().Set("Content-Type", contentType) |
||||
w.Header().Set("Content-Length", strconv.Itoa(len(data))) |
||||
w.Header().Set("Cache-Control", "public, max-age="+strconv.Itoa(cacheSeconds)) |
||||
|
||||
if _, err := w.Write(data); err != nil { |
||||
log.Println("unable to write image.") |
||||
} |
||||
} |
@ -1,4 +1,4 @@
@@ -1,4 +1,4 @@
|
||||
package controllers |
||||
package responses |
||||
|
||||
// PaginatedResponse is a structure for returning a total count with results.
|
||||
type PaginatedResponse struct { |
@ -1,11 +1,11 @@
@@ -1,11 +1,11 @@
|
||||
package controllers |
||||
package responses |
||||
|
||||
import ( |
||||
"encoding/json" |
||||
"net/http" |
||||
|
||||
"github.com/owncast/owncast/models" |
||||
"github.com/owncast/owncast/router/middleware" |
||||
"github.com/owncast/owncast/webserver/middleware" |
||||
log "github.com/sirupsen/logrus" |
||||
) |
||||
|
@ -0,0 +1,414 @@
@@ -0,0 +1,414 @@
|
||||
package webserver |
||||
|
||||
import ( |
||||
"net/http" |
||||
|
||||
"github.com/owncast/owncast/activitypub" |
||||
"github.com/owncast/owncast/config" |
||||
"github.com/owncast/owncast/core/chat" |
||||
"github.com/owncast/owncast/core/data" |
||||
"github.com/owncast/owncast/core/user" |
||||
"github.com/owncast/owncast/utils" |
||||
fediverseauth "github.com/owncast/owncast/webserver/handlers/auth/fediverse" |
||||
"github.com/owncast/owncast/webserver/handlers/auth/indieauth" |
||||
"github.com/owncast/owncast/webserver/middleware" |
||||
"github.com/prometheus/client_golang/prometheus/promhttp" |
||||
) |
||||
|
||||
func (s *webServer) setupRoutes() { |
||||
s.setupWebAssetRoutes() |
||||
s.setupInternalAPIRoutes() |
||||
s.setupAdminAPIRoutes() |
||||
s.setupExternalThirdPartyAPIRoutes() |
||||
s.setupModerationAPIRoutes() |
||||
|
||||
s.router.HandleFunc("/hls/", s.handlers.HandleHLSRequest) |
||||
|
||||
// websocket
|
||||
s.router.HandleFunc("/ws", func(w http.ResponseWriter, r *http.Request) { |
||||
chat.HandleClientConnection(w, r) |
||||
}) |
||||
|
||||
s.router.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { |
||||
// This is a hack because Prometheus enables this endpoint by default
|
||||
// due to its use of expvar and we do not want this exposed.
|
||||
if r.URL.Path == "/debug/vars" { |
||||
w.WriteHeader(http.StatusNotFound) |
||||
return |
||||
} else if r.URL.Path == "/embed/chat/" || r.URL.Path == "/embed/chat" { |
||||
// Redirect /embed/chat
|
||||
http.Redirect(w, r, "/embed/chat/readonly", http.StatusTemporaryRedirect) |
||||
} else { |
||||
s.handlers.IndexHandler(w, r) |
||||
// s.ServeHTTP(w, r)
|
||||
} |
||||
}) |
||||
|
||||
// ActivityPub has its own router
|
||||
activitypub.Start(data.GetDatastore(), s.router) |
||||
} |
||||
|
||||
func (s *webServer) setupWebAssetRoutes() { |
||||
// The admin web app.
|
||||
s.router.HandleFunc("/admin/", middleware.RequireAdminAuth(s.handlers.IndexHandler)) |
||||
|
||||
// Images
|
||||
s.router.HandleFunc("/thumbnail.jpg", s.handlers.GetThumbnail) |
||||
s.router.HandleFunc("/preview.gif", s.handlers.GetPreview) |
||||
s.router.HandleFunc("/logo", s.handlers.GetLogo) |
||||
|
||||
// Custom Javascript
|
||||
s.router.HandleFunc("/customjavascript", s.handlers.ServeCustomJavascript) |
||||
|
||||
// Return a single emoji image.
|
||||
s.router.HandleFunc(config.EmojiDir, s.handlers.GetCustomEmojiImage) |
||||
|
||||
// return the logo
|
||||
|
||||
// return a logo that's compatible with external social networks
|
||||
s.router.HandleFunc("/logo/external", s.handlers.GetCompatibleLogo) |
||||
|
||||
// robots.txt
|
||||
s.router.HandleFunc("/robots.txt", s.handlers.GetRobotsDotTxt) |
||||
|
||||
// Optional public static files
|
||||
s.router.Handle("/public/", http.StripPrefix("/public/", http.FileServer(http.Dir(config.PublicFilesPath)))) |
||||
} |
||||
|
||||
func (s *webServer) setupInternalAPIRoutes() { |
||||
// Internal APIs
|
||||
|
||||
// status of the system
|
||||
s.router.HandleFunc("/api/status", s.handlers.GetStatus) |
||||
|
||||
// custom emoji supported in the chat
|
||||
s.router.HandleFunc("/api/emoji", s.handlers.GetCustomEmojiList) |
||||
|
||||
// chat history api
|
||||
s.router.HandleFunc("/api/chat", middleware.RequireUserAccessToken(s.handlers.GetChatMessages)) |
||||
|
||||
// web config api
|
||||
s.router.HandleFunc("/api/config", s.handlers.GetWebConfig) |
||||
|
||||
// return the YP protocol data
|
||||
s.router.HandleFunc("/api/yp", s.handlers.GetYPResponse) |
||||
|
||||
// list of all social platforms
|
||||
s.router.HandleFunc("/api/socialplatforms", s.handlers.GetAllSocialPlatforms) |
||||
|
||||
// return the list of video variants available
|
||||
s.router.HandleFunc("/api/video/variants", s.handlers.GetVideoStreamOutputVariants) |
||||
|
||||
// tell the backend you're an active viewer
|
||||
s.router.HandleFunc("/api/ping", s.handlers.Ping) |
||||
|
||||
// register a new chat user
|
||||
s.router.HandleFunc("/api/chat/register", s.handlers.RegisterAnonymousChatUser) |
||||
|
||||
// return remote follow details
|
||||
s.router.HandleFunc("/api/remotefollow", s.handlers.RemoteFollow) |
||||
|
||||
// return followers
|
||||
s.router.HandleFunc("/api/followers", middleware.HandlePagination(s.handlers.GetFollowers)) |
||||
|
||||
// save client video playback metrics
|
||||
s.router.HandleFunc("/api/metrics/playback", s.handlers.ReportPlaybackMetrics) |
||||
|
||||
// Register for notifications
|
||||
s.router.HandleFunc("/api/notifications/register", middleware.RequireUserAccessToken(s.handlers.RegisterForLiveNotifications)) |
||||
|
||||
// Start auth flow
|
||||
s.router.HandleFunc("/api/auth/indieauth", middleware.RequireUserAccessToken(indieauth.StartAuthFlow)) |
||||
s.router.HandleFunc("/api/auth/indieauth/callback", indieauth.HandleRedirect) |
||||
s.router.HandleFunc("/api/auth/provider/indieauth", indieauth.HandleAuthEndpoint) |
||||
|
||||
s.router.HandleFunc("/api/auth/fediverse", middleware.RequireUserAccessToken(fediverseauth.RegisterFediverseOTPRequest)) |
||||
s.router.HandleFunc("/api/auth/fediverse/verify", fediverseauth.VerifyFediverseOTPRequest) |
||||
} |
||||
|
||||
func (s *webServer) setupAdminAPIRoutes() { |
||||
// Current inbound broadcaster
|
||||
s.router.HandleFunc("/api/admin/status", middleware.RequireAdminAuth(s.handlers.GetAdminStatus)) |
||||
|
||||
// Disconnect inbound stream
|
||||
s.router.HandleFunc("/api/admin/disconnect", middleware.RequireAdminAuth(s.handlers.DisconnectInboundConnection)) |
||||
|
||||
// Server config
|
||||
s.router.HandleFunc("/api/admin/serverconfig", middleware.RequireAdminAuth(s.handlers.GetServerConfig)) |
||||
|
||||
// Get viewer count over time
|
||||
s.router.HandleFunc("/api/admin/viewersOverTime", middleware.RequireAdminAuth(s.handlers.GetViewersOverTime)) |
||||
|
||||
// Get active viewers
|
||||
s.router.HandleFunc("/api/admin/viewers", middleware.RequireAdminAuth(s.handlers.GetActiveViewers)) |
||||
|
||||
// Get hardware stats
|
||||
s.router.HandleFunc("/api/admin/hardwarestats", middleware.RequireAdminAuth(s.handlers.GetHardwareStats)) |
||||
|
||||
// Get a a detailed list of currently connected chat clients
|
||||
s.router.HandleFunc("/api/admin/chat/clients", middleware.RequireAdminAuth(s.handlers.GetConnectedChatClients)) |
||||
|
||||
// Get all logs
|
||||
s.router.HandleFunc("/api/admin/logs", middleware.RequireAdminAuth(s.handlers.GetLogs)) |
||||
|
||||
// Get warning/error logs
|
||||
s.router.HandleFunc("/api/admin/logs/warnings", middleware.RequireAdminAuth(s.handlers.GetWarnings)) |
||||
|
||||
// Get all chat messages for the admin, unfiltered.
|
||||
s.router.HandleFunc("/api/admin/chat/messages", middleware.RequireAdminAuth(s.handlers.GetAdminChatMessages)) |
||||
|
||||
// Update chat message visibility
|
||||
s.router.HandleFunc("/api/admin/chat/messagevisibility", middleware.RequireAdminAuth(s.handlers.UpdateMessageVisibility)) |
||||
|
||||
// Enable/disable a user
|
||||
s.router.HandleFunc("/api/admin/chat/users/setenabled", middleware.RequireAdminAuth(s.handlers.UpdateUserEnabled)) |
||||
|
||||
// Ban/unban an IP address
|
||||
s.router.HandleFunc("/api/admin/chat/users/ipbans/create", middleware.RequireAdminAuth(s.handlers.BanIPAddress)) |
||||
|
||||
// Remove an IP address ban
|
||||
s.router.HandleFunc("/api/admin/chat/users/ipbans/remove", middleware.RequireAdminAuth(s.handlers.UnBanIPAddress)) |
||||
|
||||
// Return all the banned IP addresses
|
||||
s.router.HandleFunc("/api/admin/chat/users/ipbans", middleware.RequireAdminAuth(s.handlers.GetIPAddressBans)) |
||||
|
||||
// Get a list of disabled users
|
||||
s.router.HandleFunc("/api/admin/chat/users/disabled", middleware.RequireAdminAuth(s.handlers.GetDisabledUsers)) |
||||
|
||||
// Set moderator status for a user
|
||||
s.router.HandleFunc("/api/admin/chat/users/setmoderator", middleware.RequireAdminAuth(s.handlers.UpdateUserModerator)) |
||||
|
||||
// Get a list of moderator users
|
||||
s.router.HandleFunc("/api/admin/chat/users/moderators", middleware.RequireAdminAuth(s.handlers.GetModerators)) |
||||
|
||||
// return followers
|
||||
s.router.HandleFunc("/api/admin/followers", middleware.RequireAdminAuth(middleware.HandlePagination(s.handlers.GetFollowers))) |
||||
|
||||
// Get a list of pending follow requests
|
||||
s.router.HandleFunc("/api/admin/followers/pending", middleware.RequireAdminAuth(s.handlers.GetPendingFollowRequests)) |
||||
|
||||
// Get a list of rejected or blocked follows
|
||||
s.router.HandleFunc("/api/admin/followers/blocked", middleware.RequireAdminAuth(s.handlers.GetBlockedAndRejectedFollowers)) |
||||
|
||||
// Set the following state of a follower or follow request.
|
||||
s.router.HandleFunc("/api/admin/followers/approve", middleware.RequireAdminAuth(s.handlers.ApproveFollower)) |
||||
|
||||
// Upload custom emoji
|
||||
s.router.HandleFunc("/api/admin/emoji/upload", middleware.RequireAdminAuth(s.handlers.UploadCustomEmoji)) |
||||
|
||||
// Delete custom emoji
|
||||
s.router.HandleFunc("/api/admin/emoji/delete", middleware.RequireAdminAuth(s.handlers.DeleteCustomEmoji)) |
||||
|
||||
// Update config values
|
||||
|
||||
// Change the current streaming key in memory
|
||||
s.router.HandleFunc("/api/admin/config/adminpass", middleware.RequireAdminAuth(s.handlers.SetAdminPassword)) |
||||
|
||||
// Set an array of valid stream keys
|
||||
s.router.HandleFunc("/api/admin/config/streamkeys", middleware.RequireAdminAuth(s.handlers.SetStreamKeys)) |
||||
|
||||
// Change the extra page content in memory
|
||||
s.router.HandleFunc("/api/admin/config/pagecontent", middleware.RequireAdminAuth(s.handlers.SetExtraPageContent)) |
||||
|
||||
// Stream title
|
||||
s.router.HandleFunc("/api/admin/config/streamtitle", middleware.RequireAdminAuth(s.handlers.SetStreamTitle)) |
||||
|
||||
// Server name
|
||||
s.router.HandleFunc("/api/admin/config/name", middleware.RequireAdminAuth(s.handlers.SetServerName)) |
||||
|
||||
// Server summary
|
||||
s.router.HandleFunc("/api/admin/config/serversummary", middleware.RequireAdminAuth(s.handlers.SetServerSummary)) |
||||
|
||||
// Offline message
|
||||
s.router.HandleFunc("/api/admin/config/offlinemessage", middleware.RequireAdminAuth(s.handlers.SetCustomOfflineMessage)) |
||||
|
||||
// Server welcome message
|
||||
s.router.HandleFunc("/api/admin/config/welcomemessage", middleware.RequireAdminAuth(s.handlers.SetServerWelcomeMessage)) |
||||
|
||||
// Disable chat
|
||||
s.router.HandleFunc("/api/admin/config/chat/disable", middleware.RequireAdminAuth(s.handlers.SetChatDisabled)) |
||||
|
||||
// Disable chat user join messages
|
||||
s.router.HandleFunc("/api/admin/config/chat/joinmessagesenabled", middleware.RequireAdminAuth(s.handlers.SetChatJoinMessagesEnabled)) |
||||
|
||||
// Enable/disable chat established user mode
|
||||
s.router.HandleFunc("/api/admin/config/chat/establishedusermode", middleware.RequireAdminAuth(s.handlers.SetEnableEstablishedChatUserMode)) |
||||
|
||||
// Set chat usernames that are not allowed
|
||||
s.router.HandleFunc("/api/admin/config/chat/forbiddenusernames", middleware.RequireAdminAuth(s.handlers.SetForbiddenUsernameList)) |
||||
|
||||
// Set the suggested chat usernames that will be assigned automatically
|
||||
s.router.HandleFunc("/api/admin/config/chat/suggestedusernames", middleware.RequireAdminAuth(s.handlers.SetSuggestedUsernameList)) |
||||
|
||||
// Set video codec
|
||||
s.router.HandleFunc("/api/admin/config/video/codec", middleware.RequireAdminAuth(s.handlers.SetVideoCodec)) |
||||
|
||||
// Set style/color/css values
|
||||
s.router.HandleFunc("/api/admin/config/appearance", middleware.RequireAdminAuth(s.handlers.SetCustomColorVariableValues)) |
||||
|
||||
// Return all webhooks
|
||||
s.router.HandleFunc("/api/admin/webhooks", middleware.RequireAdminAuth(s.handlers.GetWebhooks)) |
||||
|
||||
// Delete a single webhook
|
||||
s.router.HandleFunc("/api/admin/webhooks/delete", middleware.RequireAdminAuth(s.handlers.DeleteWebhook)) |
||||
|
||||
// Create a single webhook
|
||||
s.router.HandleFunc("/api/admin/webhooks/create", middleware.RequireAdminAuth(s.handlers.CreateWebhook)) |
||||
|
||||
// Get all access tokens
|
||||
s.router.HandleFunc("/api/admin/accesstokens", middleware.RequireAdminAuth(s.handlers.GetExternalAPIUsers)) |
||||
|
||||
// Delete a single access token
|
||||
s.router.HandleFunc("/api/admin/accesstokens/delete", middleware.RequireAdminAuth(s.handlers.DeleteExternalAPIUser)) |
||||
|
||||
// Create a single access token
|
||||
s.router.HandleFunc("/api/admin/accesstokens/create", middleware.RequireAdminAuth(s.handlers.CreateExternalAPIUser)) |
||||
|
||||
// Return the auto-update features that are supported for this instance.
|
||||
s.router.HandleFunc("/api/admin/update/options", middleware.RequireAdminAuth(s.handlers.AutoUpdateOptions)) |
||||
|
||||
// Begin the auto update
|
||||
s.router.HandleFunc("/api/admin/update/start", middleware.RequireAdminAuth(s.handlers.AutoUpdateStart)) |
||||
|
||||
// Force quit the service to restart it
|
||||
s.router.HandleFunc("/api/admin/update/forcequit", middleware.RequireAdminAuth(s.handlers.AutoUpdateForceQuit)) |
||||
|
||||
// Logo path
|
||||
s.router.HandleFunc("/api/admin/config/logo", middleware.RequireAdminAuth(s.handlers.SetLogo)) |
||||
|
||||
// Server tags
|
||||
s.router.HandleFunc("/api/admin/config/tags", middleware.RequireAdminAuth(s.handlers.SetTags)) |
||||
|
||||
// ffmpeg
|
||||
s.router.HandleFunc("/api/admin/config/ffmpegpath", middleware.RequireAdminAuth(s.handlers.SetFfmpegPath)) |
||||
|
||||
// Server http port
|
||||
s.router.HandleFunc("/api/admin/config/webserverport", middleware.RequireAdminAuth(s.handlers.SetWebServerPort)) |
||||
|
||||
// Server http listen address
|
||||
s.router.HandleFunc("/api/admin/config/webserverip", middleware.RequireAdminAuth(s.handlers.SetWebServerIP)) |
||||
|
||||
// Server rtmp port
|
||||
s.router.HandleFunc("/api/admin/config/rtmpserverport", middleware.RequireAdminAuth(s.handlers.SetRTMPServerPort)) |
||||
|
||||
// Websocket host override
|
||||
s.router.HandleFunc("/api/admin/config/sockethostoverride", middleware.RequireAdminAuth(s.handlers.SetSocketHostOverride)) |
||||
|
||||
// Custom video serving endpoint
|
||||
s.router.HandleFunc("/api/admin/config/videoservingendpoint", middleware.RequireAdminAuth(s.handlers.SetVideoServingEndpoint)) |
||||
|
||||
// Is server marked as NSFW
|
||||
s.router.HandleFunc("/api/admin/config/nsfw", middleware.RequireAdminAuth(s.handlers.SetNSFW)) |
||||
|
||||
// directory enabled
|
||||
s.router.HandleFunc("/api/admin/config/directoryenabled", middleware.RequireAdminAuth(s.handlers.SetDirectoryEnabled)) |
||||
|
||||
// social handles
|
||||
s.router.HandleFunc("/api/admin/config/socialhandles", middleware.RequireAdminAuth(s.handlers.SetSocialHandles)) |
||||
|
||||
// set the number of video segments and duration per segment in a playlist
|
||||
s.router.HandleFunc("/api/admin/config/video/streamlatencylevel", middleware.RequireAdminAuth(s.handlers.SetStreamLatencyLevel)) |
||||
|
||||
// set an array of video output configurations
|
||||
s.router.HandleFunc("/api/admin/config/video/streamoutputvariants", middleware.RequireAdminAuth(s.handlers.SetStreamOutputVariants)) |
||||
|
||||
// set s3 configuration
|
||||
s.router.HandleFunc("/api/admin/config/s3", middleware.RequireAdminAuth(s.handlers.SetS3Configuration)) |
||||
|
||||
// set server url
|
||||
s.router.HandleFunc("/api/admin/config/serverurl", middleware.RequireAdminAuth(s.handlers.SetServerURL)) |
||||
|
||||
// reset the YP registration
|
||||
s.router.HandleFunc("/api/admin/yp/reset", middleware.RequireAdminAuth(s.handlers.ResetYPRegistration)) |
||||
|
||||
// set external action links
|
||||
s.router.HandleFunc("/api/admin/config/externalactions", middleware.RequireAdminAuth(s.handlers.SetExternalActions)) |
||||
|
||||
// set custom style css
|
||||
s.router.HandleFunc("/api/admin/config/customstyles", middleware.RequireAdminAuth(s.handlers.SetCustomStyles)) |
||||
|
||||
// set custom style javascript
|
||||
s.router.HandleFunc("/api/admin/config/customjavascript", middleware.RequireAdminAuth(s.handlers.SetCustomJavascript)) |
||||
|
||||
// Video playback metrics
|
||||
s.router.HandleFunc("/api/admin/metrics/video", middleware.RequireAdminAuth(s.handlers.GetVideoPlaybackMetrics)) |
||||
|
||||
// Is the viewer count hidden from viewers
|
||||
s.router.HandleFunc("/api/admin/config/hideviewercount", middleware.RequireAdminAuth(s.handlers.SetHideViewerCount)) |
||||
|
||||
// set disabling of search indexing
|
||||
s.router.HandleFunc("/api/admin/config/disablesearchindexing", middleware.RequireAdminAuth(s.handlers.SetDisableSearchIndexing)) |
||||
|
||||
// enable/disable federation features
|
||||
s.router.HandleFunc("/api/admin/config/federation/enable", middleware.RequireAdminAuth(s.handlers.SetFederationEnabled)) |
||||
|
||||
// set if federation activities are private
|
||||
s.router.HandleFunc("/api/admin/config/federation/private", middleware.RequireAdminAuth(s.handlers.SetFederationActivityPrivate)) |
||||
|
||||
// set if fediverse engagement appears in chat
|
||||
s.router.HandleFunc("/api/admin/config/federation/showengagement", middleware.RequireAdminAuth(s.handlers.SetFederationShowEngagement)) |
||||
|
||||
// set local federated username
|
||||
s.router.HandleFunc("/api/admin/config/federation/username", middleware.RequireAdminAuth(s.handlers.SetFederationUsername)) |
||||
|
||||
// set federated go live message
|
||||
s.router.HandleFunc("/api/admin/config/federation/livemessage", middleware.RequireAdminAuth(s.handlers.SetFederationGoLiveMessage)) |
||||
|
||||
// Federation blocked domains
|
||||
s.router.HandleFunc("/api/admin/config/federation/blockdomains", middleware.RequireAdminAuth(s.handlers.SetFederationBlockDomains)) |
||||
|
||||
// send a public message to the Fediverse from the server's user
|
||||
s.router.HandleFunc("/api/admin/federation/send", middleware.RequireAdminAuth(s.handlers.SendFederatedMessage)) |
||||
|
||||
// Return federated activities
|
||||
s.router.HandleFunc("/api/admin/federation/actions", middleware.RequireAdminAuth(middleware.HandlePagination(s.handlers.GetFederatedActions))) |
||||
|
||||
// Prometheus metrics
|
||||
s.router.Handle("/api/admin/prometheus", middleware.RequireAdminAuth(func(rw http.ResponseWriter, r *http.Request) { |
||||
promhttp.Handler().ServeHTTP(rw, r) |
||||
})) |
||||
|
||||
// Configure outbound notification channels.
|
||||
s.router.HandleFunc("/api/admin/config/notifications/discord", middleware.RequireAdminAuth(s.handlers.SetDiscordNotificationConfiguration)) |
||||
s.router.HandleFunc("/api/admin/config/notifications/browser", middleware.RequireAdminAuth(s.handlers.SetBrowserNotificationConfiguration)) |
||||
} |
||||
|
||||
func (s *webServer) setupExternalThirdPartyAPIRoutes() { |
||||
// Send a system message to chat
|
||||
s.router.HandleFunc("/api/integrations/chat/system", middleware.RequireExternalAPIAccessToken(user.ScopeCanSendSystemMessages, s.handlers.SendSystemMessage)) |
||||
|
||||
// Send a system message to a single client
|
||||
s.router.HandleFunc(utils.RestEndpoint("/api/integrations/chat/system/client/{clientId}", middleware.RequireExternalAPIAccessToken(user.ScopeCanSendSystemMessages, s.handlers.SendSystemMessageToConnectedClient))) |
||||
|
||||
// Send a user message to chat *NO LONGER SUPPORTED
|
||||
s.router.HandleFunc("/api/integrations/chat/user", middleware.RequireExternalAPIAccessToken(user.ScopeCanSendChatMessages, s.handlers.SendUserMessage)) |
||||
|
||||
// Send a message to chat as a specific 3rd party bot/integration based on its access token
|
||||
s.router.HandleFunc("/api/integrations/chat/send", middleware.RequireExternalAPIAccessToken(user.ScopeCanSendChatMessages, s.handlers.SendIntegrationChatMessage)) |
||||
|
||||
// Send a user action to chat
|
||||
s.router.HandleFunc("/api/integrations/chat/action", middleware.RequireExternalAPIAccessToken(user.ScopeCanSendSystemMessages, s.handlers.SendChatAction)) |
||||
|
||||
// Hide chat message
|
||||
s.router.HandleFunc("/api/integrations/chat/messagevisibility", middleware.RequireExternalAPIAccessToken(user.ScopeHasAdminAccess, s.handlers.ExternalUpdateMessageVisibility)) |
||||
|
||||
// Stream title
|
||||
s.router.HandleFunc("/api/integrations/streamtitle", middleware.RequireExternalAPIAccessToken(user.ScopeHasAdminAccess, s.handlers.ExternalSetStreamTitle)) |
||||
|
||||
// Get chat history
|
||||
s.router.HandleFunc("/api/integrations/chat", middleware.RequireExternalAPIAccessToken(user.ScopeHasAdminAccess, s.handlers.ExternalGetChatMessages)) |
||||
|
||||
// Connected clients
|
||||
s.router.HandleFunc("/api/integrations/clients", middleware.RequireExternalAPIAccessToken(user.ScopeHasAdminAccess, s.handlers.ExternalGetConnectedChatClients)) |
||||
} |
||||
|
||||
func (s *webServer) setupModerationAPIRoutes() { |
||||
// Update chat message visibility
|
||||
s.router.HandleFunc("/api/chat/messagevisibility", middleware.RequireUserModerationScopeAccesstoken(s.handlers.UpdateMessageVisibility)) |
||||
|
||||
// Enable/disable a user
|
||||
s.router.HandleFunc("/api/chat/users/setenabled", middleware.RequireUserModerationScopeAccesstoken(s.handlers.UpdateUserEnabled)) |
||||
|
||||
// Get a user's details
|
||||
s.router.HandleFunc("/api/moderation/chat/user/", middleware.RequireUserModerationScopeAccesstoken(s.handlers.GetUserDetails)) |
||||
} |
@ -0,0 +1,54 @@
@@ -0,0 +1,54 @@
|
||||
package webserver |
||||
|
||||
import ( |
||||
"fmt" |
||||
"net/http" |
||||
"time" |
||||
|
||||
"github.com/CAFxX/httpcompression" |
||||
"github.com/owncast/owncast/webserver/handlers" |
||||
log "github.com/sirupsen/logrus" |
||||
"golang.org/x/net/http2" |
||||
"golang.org/x/net/http2/h2c" |
||||
) |
||||
|
||||
type webServer struct { |
||||
router *http.ServeMux |
||||
handlers *handlers.Handlers |
||||
server *http.Server |
||||
} |
||||
|
||||
func New() *webServer { |
||||
s := &webServer{ |
||||
router: http.NewServeMux(), |
||||
} |
||||
|
||||
s.setupRoutes() |
||||
|
||||
return s |
||||
} |
||||
|
||||
func (s *webServer) Start(listenIP string, listenPort int) error { |
||||
compress, _ := httpcompression.DefaultAdapter() // Use the default configuration
|
||||
h2s := &http2.Server{} |
||||
http2Router := h2c.NewHandler(s.router, h2s) |
||||
|
||||
s.server = &http.Server{ |
||||
Addr: fmt.Sprintf("%s:%d", listenIP, listenPort), |
||||
ReadHeaderTimeout: 4 * time.Second, |
||||
Handler: compress(http2Router), |
||||
} |
||||
|
||||
if listenIP != "0.0.0.0" { |
||||
log.Infof("Web server is listening at %s:%d.", listenIP, listenPort) |
||||
} else { |
||||
log.Infof("Web server is listening on port %d.", listenPort) |
||||
} |
||||
|
||||
return s.server.ListenAndServe() |
||||
} |
||||
|
||||
// ServeHTTP is the entry point for all web requests.
|
||||
func (s *webServer) ServeHTTP(w http.ResponseWriter, r *http.Request) { |
||||
s.router.ServeHTTP(w, r) |
||||
} |
@ -0,0 +1,35 @@
@@ -0,0 +1,35 @@
|
||||
package webserver |
||||
|
||||
import ( |
||||
"net/http" |
||||
"net/http/httptest" |
||||
"os" |
||||
"testing" |
||||
|
||||
"github.com/owncast/owncast/core/data" |
||||
) |
||||
|
||||
var srv *webServer |
||||
|
||||
func TestMain(m *testing.M) { |
||||
dbFile, err := os.CreateTemp(os.TempDir(), "owncast-test-db.db") |
||||
if err != nil { |
||||
panic(err) |
||||
} |
||||
|
||||
data.SetupPersistence(dbFile.Name()) |
||||
srv = New() |
||||
|
||||
m.Run() |
||||
} |
||||
|
||||
// TestPrometheusPath tests that the /debug/vars endpoint that
|
||||
// Prometheus automatically enables is not exposed.
|
||||
func TestPrometheusDebugPath(t *testing.T) { |
||||
r := httptest.NewRequest(http.MethodGet, "/debug/vars", nil) |
||||
w := httptest.NewRecorder() |
||||
srv.ServeHTTP(w, r) |
||||
if w.Result().StatusCode != http.StatusNotFound { |
||||
t.Errorf("Expected 404, got %d", w.Result().StatusCode) |
||||
} |
||||
} |
Loading…
Reference in new issue