Outbound live stream notifications (#1663)

* First pass at browser, discord, twilio notifications

* Commit updated Javascript packages

* Remove twilio notification support

* Email notifications/smtp support

* Fix Firefox notification support, remove chrome checks

* WIP more email work

* Add support for twitter notifications

* Add stream title to discord and twitter notifications

* Update notification registration modal

* Fix hide/show email section

* Commit updated API documentation

* Commit updated Javascript packages

* Fix post-rebase missing var

* Remove unused var

* Handle unsubscribe errors for browser push

* Standardize email config prop names

* Allow overriding go live email template

* Some notifications cleanup

* Commit updated Javascript packages

* Remove email/smtp/mailjet support

* Remove more references to email notifications

Co-authored-by: Owncast <owncast@owncast.online>
This commit is contained in:
Gabe Kangas
2022-03-18 13:33:23 -07:00
committed by GitHub
parent 4e415f7257
commit 4a17f30da8
39 changed files with 2209 additions and 3313 deletions

View File

@@ -0,0 +1,85 @@
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")
}
// SetTwitterConfiguration will set the browser notification configuration.
func SetTwitterConfiguration(w http.ResponseWriter, r *http.Request) {
if !requirePOST(w, r) {
return
}
type request struct {
Value models.TwitterConfiguration `json:"value"`
}
decoder := json.NewDecoder(r.Body)
var config request
if err := decoder.Decode(&config); err != nil {
controllers.WriteSimpleResponse(w, false, "unable to update twitter config with provided values")
return
}
if err := data.SetTwitterConfiguration(config.Value); err != nil {
controllers.WriteSimpleResponse(w, false, "unable to update twitter config with provided values")
return
}
controllers.WriteSimpleResponse(w, true, "updated twitter config with provided values")
}

View File

@@ -77,6 +77,11 @@ func GetServerConfig(w http.ResponseWriter, r *http.Request) {
ShowEngagement: data.GetFederationShowEngagement(),
BlockedDomains: data.GetBlockedFederatedDomains(),
},
Notifications: notificationsConfigResponse{
Discord: data.GetDiscordConfig(),
Browser: data.GetBrowserPushConfig(),
Twitter: data.GetTwitterConfiguration(),
},
}
w.Header().Set("Content-Type", "application/json")
@@ -88,25 +93,26 @@ func GetServerConfig(w http.ResponseWriter, r *http.Request) {
}
type serverConfigAdminResponse struct {
InstanceDetails webConfigResponse `json:"instanceDetails"`
FFmpegPath string `json:"ffmpegPath"`
StreamKey string `json:"streamKey"`
WebServerPort int `json:"webServerPort"`
WebServerIP string `json:"webServerIP"`
RTMPServerPort int `json:"rtmpServerPort"`
S3 models.S3 `json:"s3"`
VideoSettings videoSettings `json:"videoSettings"`
YP yp `json:"yp"`
ChatDisabled bool `json:"chatDisabled"`
ChatJoinMessagesEnabled bool `json:"chatJoinMessagesEnabled"`
ChatEstablishedUserMode bool `json:"chatEstablishedUserMode"`
ExternalActions []models.ExternalAction `json:"externalActions"`
SupportedCodecs []string `json:"supportedCodecs"`
VideoCodec string `json:"videoCodec"`
ForbiddenUsernames []string `json:"forbiddenUsernames"`
Federation federationConfigResponse `json:"federation"`
SuggestedUsernames []string `json:"suggestedUsernames"`
SocketHostOverride string `json:"socketHostOverride,omitempty"`
InstanceDetails webConfigResponse `json:"instanceDetails"`
FFmpegPath string `json:"ffmpegPath"`
StreamKey string `json:"streamKey"`
WebServerPort int `json:"webServerPort"`
WebServerIP string `json:"webServerIP"`
RTMPServerPort int `json:"rtmpServerPort"`
S3 models.S3 `json:"s3"`
VideoSettings videoSettings `json:"videoSettings"`
YP yp `json:"yp"`
ChatDisabled bool `json:"chatDisabled"`
ChatJoinMessagesEnabled bool `json:"chatJoinMessagesEnabled"`
ChatEstablishedUserMode bool `json:"chatEstablishedUserMode"`
ExternalActions []models.ExternalAction `json:"externalActions"`
SupportedCodecs []string `json:"supportedCodecs"`
VideoCodec string `json:"videoCodec"`
ForbiddenUsernames []string `json:"forbiddenUsernames"`
Federation federationConfigResponse `json:"federation"`
SuggestedUsernames []string `json:"suggestedUsernames"`
SocketHostOverride string `json:"socketHostOverride,omitempty"`
Notifications notificationsConfigResponse `json:"notifications"`
}
type videoSettings struct {
@@ -142,3 +148,9 @@ type federationConfigResponse struct {
ShowEngagement bool `json:"showEngagement"`
BlockedDomains []string `json:"blockedDomains"`
}
type notificationsConfigResponse struct {
Browser models.BrowserNotificationConfiguration `json:"browser"`
Discord models.DiscordConfiguration `json:"discord"`
Twitter models.TwitterConfiguration `json:"twitter"`
}

View File

@@ -12,24 +12,26 @@ import (
"github.com/owncast/owncast/models"
"github.com/owncast/owncast/router/middleware"
"github.com/owncast/owncast/utils"
log "github.com/sirupsen/logrus"
)
type webConfigResponse struct {
Name string `json:"name"`
Summary string `json:"summary"`
Logo string `json:"logo"`
Tags []string `json:"tags"`
Version string `json:"version"`
NSFW bool `json:"nsfw"`
SocketHostOverride string `json:"socketHostOverride,omitempty"`
ExtraPageContent string `json:"extraPageContent"`
StreamTitle string `json:"streamTitle,omitempty"` // What's going on with the current stream
SocialHandles []models.SocialHandle `json:"socialHandles"`
ChatDisabled bool `json:"chatDisabled"`
ExternalActions []models.ExternalAction `json:"externalActions"`
CustomStyles string `json:"customStyles"`
MaxSocketPayloadSize int `json:"maxSocketPayloadSize"`
Federation federationConfigResponse `json:"federation"`
Name string `json:"name"`
Summary string `json:"summary"`
Logo string `json:"logo"`
Tags []string `json:"tags"`
Version string `json:"version"`
NSFW bool `json:"nsfw"`
SocketHostOverride string `json:"socketHostOverride,omitempty"`
ExtraPageContent string `json:"extraPageContent"`
StreamTitle string `json:"streamTitle,omitempty"` // What's going on with the current stream
SocialHandles []models.SocialHandle `json:"socialHandles"`
ChatDisabled bool `json:"chatDisabled"`
ExternalActions []models.ExternalAction `json:"externalActions"`
CustomStyles string `json:"customStyles"`
MaxSocketPayloadSize int `json:"maxSocketPayloadSize"`
Federation federationConfigResponse `json:"federation"`
Notifications notificationsConfigResponse `json:"notifications"`
}
type federationConfigResponse struct {
@@ -38,6 +40,15 @@ type federationConfigResponse struct {
FollowerCount int `json:"followerCount,omitempty"`
}
type browserNotificationsConfigResponse struct {
Enabled bool `json:"enabled"`
PublicKey string `json:"publicKey,omitempty"`
}
type notificationsConfigResponse struct {
Browser browserNotificationsConfigResponse `json:"browser"`
}
// GetWebConfig gets the status of the server.
func GetWebConfig(w http.ResponseWriter, r *http.Request) {
middleware.EnableCors(w)
@@ -72,6 +83,20 @@ func GetWebConfig(w http.ResponseWriter, r *http.Request) {
}
}
browserPushEnabled := data.GetBrowserPushConfig().Enabled
browserPushPublicKey, err := data.GetBrowserPushPublicKey()
if err != nil {
log.Errorln("unable to fetch browser push notifications public key", err)
browserPushEnabled = false
}
notificationsResponse := notificationsConfigResponse{
Browser: browserNotificationsConfigResponse{
Enabled: browserPushEnabled,
PublicKey: browserPushPublicKey,
},
}
configuration := webConfigResponse{
Name: data.GetServerName(),
Summary: serverSummary,
@@ -88,6 +113,7 @@ func GetWebConfig(w http.ResponseWriter, r *http.Request) {
CustomStyles: data.GetCustomStyles(),
MaxSocketPayloadSize: config.MaxSocketPayloadSize,
Federation: federationResponse,
Notifications: notificationsResponse,
}
if err := json.NewEncoder(w).Encode(configuration); err != nil {

View File

@@ -0,0 +1,50 @@
package controllers
import (
"encoding/json"
"net/http"
"github.com/owncast/owncast/notifications"
"github.com/owncast/owncast/utils"
log "github.com/sirupsen/logrus"
)
// RegisterForLiveNotifications will register a channel + destination to be
// notified when a stream goes live.
func RegisterForLiveNotifications(w http.ResponseWriter, r *http.Request) {
if r.Method != POST {
WriteSimpleResponse(w, false, r.Method+" not supported")
return
}
type request struct {
// Channel is the notification channel (browser, sms, etc)
Channel string `json:"channel"`
// Destination is the target of the notification in the above channel.
Destination string `json:"destination"`
}
decoder := json.NewDecoder(r.Body)
var req request
if err := decoder.Decode(&req); err != nil {
log.Errorln(err)
WriteSimpleResponse(w, false, "unable to register for notifications")
return
}
// Make sure the requested channel is one we want to handle.
validTypes := []string{notifications.BrowserPushNotification}
_, validChannel := utils.FindInSlice(validTypes, req.Channel)
if !validChannel {
WriteSimpleResponse(w, false, "invalid notification channel: "+req.Channel)
return
}
if err := notifications.AddNotification(req.Channel, req.Destination); err != nil {
log.Errorln(err)
WriteSimpleResponse(w, false, "unable to save notification")
return
}
}