diff options
author | Gabe Kangas <gabek@real-ity.com> | 2022-01-18 19:29:00 -0800 |
---|---|---|
committer | Gabe Kangas <gabek@real-ity.com> | 2022-01-18 19:29:00 -0800 |
commit | 627169fc73319481b60872830d120954e0a28896 (patch) | |
tree | 8e2788ed6022e00511f2fe83ccecb50789475f70 | |
parent | a621e920e7e10f4b6e8054f7135abe6d650b129e (diff) |
Add support for established user mode. #1587
-rw-r--r-- | config/defaults.go | 10 | ||||
-rw-r--r-- | controllers/admin/chat.go | 22 | ||||
-rw-r--r-- | controllers/admin/serverConfig.go | 46 | ||||
-rw-r--r-- | core/chat/server.go | 11 | ||||
-rw-r--r-- | core/data/config.go | 94 | ||||
-rw-r--r-- | router/router.go | 3 | ||||
-rw-r--r-- | test/automated/api/chatmoderation.test.js | 37 | ||||
-rw-r--r-- | test/automated/api/package-lock.json | 2 |
8 files changed, 160 insertions, 65 deletions
diff --git a/config/defaults.go b/config/defaults.go index 6b5bd1d90..964756af1 100644 --- a/config/defaults.go +++ b/config/defaults.go @@ -1,6 +1,10 @@ package config -import "github.com/owncast/owncast/models" +import ( + "time" + + "github.com/owncast/owncast/models" +) // Defaults will hold default configuration values. type Defaults struct { @@ -27,6 +31,8 @@ type Defaults struct { FederationUsername string FederationGoLiveMessage string + + ChatEstablishedUserModeTimeDuration time.Duration } // GetDefaults will return default configuration values. @@ -54,6 +60,8 @@ func GetDefaults() Defaults { RTMPServerPort: 1935, StreamKey: "abc123", + ChatEstablishedUserModeTimeDuration: time.Minute * 15, + StreamVariants: []models.StreamOutputVariant{ { IsAudioPassthrough: true, diff --git a/controllers/admin/chat.go b/controllers/admin/chat.go index 91bdba132..59d9d197a 100644 --- a/controllers/admin/chat.go +++ b/controllers/admin/chat.go @@ -12,6 +12,7 @@ import ( "github.com/owncast/owncast/controllers" "github.com/owncast/owncast/core/chat" "github.com/owncast/owncast/core/chat/events" + "github.com/owncast/owncast/core/data" "github.com/owncast/owncast/core/user" "github.com/owncast/owncast/utils" log "github.com/sirupsen/logrus" @@ -267,3 +268,24 @@ func SendChatAction(integration user.ExternalAPIUser, w http.ResponseWriter, r * controllers.WriteSimpleResponse(w, true, "sent") } + +// SetEnableEstablishedChatUserMode sets the requirement for a chat user +// to be "established" for some time before taking part in chat. +func SetEnableEstablishedChatUserMode(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 established user only mode") + return + } + + if err := data.SetChatEstablishedUsersOnlyMode(configValue.Value.(bool)); err != nil { + controllers.WriteSimpleResponse(w, false, err.Error()) + return + } + + controllers.WriteSimpleResponse(w, true, "chat established users only mode updated") +} diff --git a/controllers/admin/serverConfig.go b/controllers/admin/serverConfig.go index 046dec6b6..0b22d8ddd 100644 --- a/controllers/admin/serverConfig.go +++ b/controllers/admin/serverConfig.go @@ -46,12 +46,13 @@ func GetServerConfig(w http.ResponseWriter, r *http.Request) { NSFW: data.GetNSFW(), CustomStyles: data.GetCustomStyles(), }, - FFmpegPath: ffmpeg, - StreamKey: data.GetStreamKey(), - WebServerPort: config.WebServerPort, - WebServerIP: config.WebServerIP, - RTMPServerPort: data.GetRTMPPortNumber(), - ChatDisabled: data.GetChatDisabled(), + FFmpegPath: ffmpeg, + StreamKey: data.GetStreamKey(), + WebServerPort: config.WebServerPort, + WebServerIP: config.WebServerIP, + RTMPServerPort: data.GetRTMPPortNumber(), + ChatDisabled: data.GetChatDisabled(), + ChatEstablishedUserMode: data.GetChatEstbalishedUsersOnlyMode(), VideoSettings: videoSettings{ VideoQualityVariants: videoQualityVariants, LatencyLevel: data.GetStreamLatencyLevel().Level, @@ -85,22 +86,23 @@ func GetServerConfig(w http.ResponseWriter, r *http.Request) { } type serverConfigAdminResponse struct { - InstanceDetails webConfigResponse `json:"instanceDetails"` - FFmpegPath string `json:"ffmpegPath"` - StreamKey string `json:"streamKey"` - WebServerPort int `json:"webServerPort"` - WebServerIP string `json:"webServerIP"` - RTMPServerPort int `json:"rtmpServerPort"` - S3 models.S3 `json:"s3"` - VideoSettings videoSettings `json:"videoSettings"` - YP yp `json:"yp"` - ChatDisabled bool `json:"chatDisabled"` - ExternalActions []models.ExternalAction `json:"externalActions"` - SupportedCodecs []string `json:"supportedCodecs"` - VideoCodec string `json:"videoCodec"` - ForbiddenUsernames []string `json:"forbiddenUsernames"` - Federation federationConfigResponse `json:"federation"` - SuggestedUsernames []string `json:"suggestedUsernames"` + 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"` + 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"` } type videoSettings struct { diff --git a/core/chat/server.go b/core/chat/server.go index 08c0317c9..8fd939faf 100644 --- a/core/chat/server.go +++ b/core/chat/server.go @@ -11,6 +11,7 @@ import ( "github.com/gorilla/websocket" + "github.com/owncast/owncast/config" "github.com/owncast/owncast/core/chat/events" "github.com/owncast/owncast/core/data" "github.com/owncast/owncast/core/user" @@ -324,6 +325,16 @@ func SendActionToUser(userID string, text string) error { } func (s *Server) eventReceived(event chatClientEvent) { + c := event.client + u := c.User + + // If established chat user only mode is enabled and the user is not old + // enough then reject this event and send them an informative message. + if u != nil && data.GetChatEstbalishedUsersOnlyMode() && time.Since(event.client.User.CreatedAt) < config.GetDefaults().ChatEstablishedUserModeTimeDuration && !u.IsModerator() { + s.sendActionToClient(c, "You have not been an established chat participant long enough to take part in chat. Please enjoy the stream and try again later.") + return + } + var typecheck map[string]interface{} if err := json.Unmarshal(event.data, &typecheck); err != nil { log.Debugln(err) diff --git a/core/data/config.go b/core/data/config.go index cc5083743..c7c4865b9 100644 --- a/core/data/config.go +++ b/core/data/config.go @@ -14,45 +14,46 @@ import ( ) const ( - extraContentKey = "extra_page_content" - streamTitleKey = "stream_title" - streamKeyKey = "stream_key" - logoPathKey = "logo_path" - serverSummaryKey = "server_summary" - serverWelcomeMessageKey = "server_welcome_message" - serverNameKey = "server_name" - serverURLKey = "server_url" - httpPortNumberKey = "http_port_number" - httpListenAddressKey = "http_listen_address" - rtmpPortNumberKey = "rtmp_port_number" - serverMetadataTagsKey = "server_metadata_tags" - directoryEnabledKey = "directory_enabled" - directoryRegistrationKeyKey = "directory_registration_key" - socialHandlesKey = "social_handles" - peakViewersSessionKey = "peak_viewers_session" - peakViewersOverallKey = "peak_viewers_overall" - lastDisconnectTimeKey = "last_disconnect_time" - ffmpegPathKey = "ffmpeg_path" - nsfwKey = "nsfw" - s3StorageEnabledKey = "s3_storage_enabled" - s3StorageConfigKey = "s3_storage_config" - videoLatencyLevel = "video_latency_level" - videoStreamOutputVariantsKey = "video_stream_output_variants" - chatDisabledKey = "chat_disabled" - externalActionsKey = "external_actions" - customStylesKey = "custom_styles" - videoCodecKey = "video_codec" - blockedUsernamesKey = "blocked_usernames" - publicKeyKey = "public_key" - privateKeyKey = "private_key" - serverInitDateKey = "server_init_date" - federationEnabledKey = "federation_enabled" - federationUsernameKey = "federation_username" - federationPrivateKey = "federation_private" - federationGoLiveMessageKey = "federation_go_live_message" - federationShowEngagementKey = "federation_show_engagement" - federationBlockedDomainsKey = "federation_blocked_domains" - suggestedUsernamesKey = "suggested_usernames" + extraContentKey = "extra_page_content" + streamTitleKey = "stream_title" + streamKeyKey = "stream_key" + logoPathKey = "logo_path" + serverSummaryKey = "server_summary" + serverWelcomeMessageKey = "server_welcome_message" + serverNameKey = "server_name" + serverURLKey = "server_url" + httpPortNumberKey = "http_port_number" + httpListenAddressKey = "http_listen_address" + rtmpPortNumberKey = "rtmp_port_number" + serverMetadataTagsKey = "server_metadata_tags" + directoryEnabledKey = "directory_enabled" + directoryRegistrationKeyKey = "directory_registration_key" + socialHandlesKey = "social_handles" + peakViewersSessionKey = "peak_viewers_session" + peakViewersOverallKey = "peak_viewers_overall" + lastDisconnectTimeKey = "last_disconnect_time" + ffmpegPathKey = "ffmpeg_path" + nsfwKey = "nsfw" + s3StorageEnabledKey = "s3_storage_enabled" + s3StorageConfigKey = "s3_storage_config" + videoLatencyLevel = "video_latency_level" + videoStreamOutputVariantsKey = "video_stream_output_variants" + chatDisabledKey = "chat_disabled" + chatEstablishedUsersOnlyModeKey = "chat_established_users_only_mode" + externalActionsKey = "external_actions" + customStylesKey = "custom_styles" + videoCodecKey = "video_codec" + blockedUsernamesKey = "blocked_usernames" + publicKeyKey = "public_key" + privateKeyKey = "private_key" + serverInitDateKey = "server_init_date" + federationEnabledKey = "federation_enabled" + federationUsernameKey = "federation_username" + federationPrivateKey = "federation_private" + federationGoLiveMessageKey = "federation_go_live_message" + federationShowEngagementKey = "federation_show_engagement" + federationBlockedDomainsKey = "federation_blocked_domains" + suggestedUsernamesKey = "suggested_usernames" ) // GetExtraPageBodyContent will return the user-supplied body content. @@ -487,6 +488,21 @@ func GetChatDisabled() bool { return false } +// SetChatEstablishedUsersOnlyMode sets the state of established user only mode. +func SetChatEstablishedUsersOnlyMode(enabled bool) error { + return _datastore.SetBool(chatEstablishedUsersOnlyModeKey, enabled) +} + +// GetChatEstbalishedUsersOnlyMode returns the state of established user only mode. +func GetChatEstbalishedUsersOnlyMode() bool { + enabled, err := _datastore.GetBool(chatEstablishedUsersOnlyModeKey) + if err == nil { + return enabled + } + + return false +} + // GetExternalActions will return the registered external actions. func GetExternalActions() []models.ExternalAction { configEntry, err := _datastore.Get(externalActionsKey) diff --git a/router/router.go b/router/router.go index 3928675d1..001afacae 100644 --- a/router/router.go +++ b/router/router.go @@ -161,6 +161,9 @@ func Start() error { // Disable chat http.HandleFunc("/api/admin/config/chat/disable", middleware.RequireAdminAuth(admin.SetChatDisabled)) + // 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)) diff --git a/test/automated/api/chatmoderation.test.js b/test/automated/api/chatmoderation.test.js index 1f01e1c99..b8f19f9f4 100644 --- a/test/automated/api/chatmoderation.test.js +++ b/test/automated/api/chatmoderation.test.js @@ -12,6 +12,11 @@ const testVisibilityMessage = { type: 'CHAT', }; +const establishedUserFailedChatMessage = { + body: 'this message should fail to send ' + Math.floor(Math.random() * 100), + type: 'CHAT', +}; + test('can send a chat message', async (done) => { const registration = await registerChat(); const accessToken = registration.accessToken; @@ -63,9 +68,39 @@ test('verify message has become hidden', async (done) => { .auth('admin', 'abc123'); const message = res.body.filter((obj) => { - return obj.body === `${testVisibilityMessage.body}`; + return obj.body === testVisibilityMessage.body; }); expect(message.length).toBe(1); expect(message[0].hiddenAt).toBeTruthy(); done(); }); + +test('can enable established chat user mode', async (done) => { + await request + .post('/api/admin/config/chat/establishedusermode') + .auth('admin', 'abc123') + .send({ value: true }) + .expect(200); + done(); +}); + +test('can send a message after established user mode is enabled', async (done) => { + const registration = await registerChat(); + const accessToken = registration.accessToken; + + sendChatMessage(establishedUserFailedChatMessage, accessToken, done); +}); + +test('verify message is not in the chat feed', async (done) => { + const res = await request + .get('/api/admin/chat/messages') + .expect(200) + .auth('admin', 'abc123'); + + const message = res.body.filter((obj) => { + return obj.body === establishedUserFailedChatMessage.body; + }); + + expect(message.length).toBe(0); + done(); +}); diff --git a/test/automated/api/package-lock.json b/test/automated/api/package-lock.json index 4e795f10d..b57527dcf 100644 --- a/test/automated/api/package-lock.json +++ b/test/automated/api/package-lock.json @@ -692,7 +692,6 @@ "jest-resolve": "^26.6.2", "jest-util": "^26.6.2", "jest-worker": "^26.6.2", - "node-notifier": "^8.0.0", "slash": "^3.0.0", "source-map": "^0.6.0", "string-length": "^4.0.1", @@ -3145,7 +3144,6 @@ "@types/node": "*", "anymatch": "^3.0.3", "fb-watchman": "^2.0.0", - "fsevents": "^2.1.2", "graceful-fs": "^4.2.4", "jest-regex-util": "^26.0.0", "jest-serializer": "^26.6.2", |