diff --git a/pkg/handlers.go b/pkg/handlers.go index 883e8f1..5c19796 100644 --- a/pkg/handlers.go +++ b/pkg/handlers.go @@ -330,6 +330,15 @@ func (s *Server) OnOpen(sub *Subscriber, msg Message) { secret := util.RandomString(16) log.Info("WebRTC: %s opens %s with secret %s", sub.Username, other.Username, secret) + // If the current user is an admin and was booted or muted, inform them. + if sub.IsAdmin() { + if other.Boots(sub.Username) { + sub.ChatServer("Note: %s had booted you off their camera before, and won't be notified of your watch.", other.Username) + } else if other.Mutes(sub.Username) { + sub.ChatServer("Note: %s had muted you before, and won't be notified of your watch.", other.Username) + } + } + // Ring the target of this request and give them the secret. other.SendJSON(Message{ Action: ActionRing, @@ -353,6 +362,14 @@ func (s *Server) OnBoot(sub *Subscriber, msg Message) { sub.booted[msg.Username] = struct{}{} sub.muteMu.Unlock() + // If the subject of the boot is an admin, inform them they have been booted. + if other, err := s.GetSubscriber(msg.Username); err == nil && other.IsAdmin() { + other.ChatServer( + "%s has booted you off of their camera!", + sub.Username, + ) + } + s.SendWhoList() } @@ -370,6 +387,14 @@ func (s *Server) OnMute(sub *Subscriber, msg Message, mute bool) { sub.muteMu.Unlock() + // If the subject of the mute is an admin, inform them they have been booted. + if other, err := s.GetSubscriber(msg.Username); err == nil && other.IsAdmin() { + other.ChatServer( + "%s has muted you! Your new mute status is: %v", + sub.Username, mute, + ) + } + // Send the Who List in case our cam will show as disabled to the muted party. s.SendWhoList() } diff --git a/pkg/websocket.go b/pkg/websocket.go index 15b7b8e..5fec31e 100644 --- a/pkg/websocket.go +++ b/pkg/websocket.go @@ -116,6 +116,11 @@ func (sub *Subscriber) ReadLoop(s *Server) { }() } +// IsAdmin safely checks if the subscriber is an admin. +func (sub *Subscriber) IsAdmin() bool { + return sub.JWTClaims != nil && sub.JWTClaims.IsAdmin +} + // SendJSON sends a JSON message to the websocket client. func (sub *Subscriber) SendJSON(v interface{}) error { data, err := json.Marshal(v) @@ -393,7 +398,7 @@ func (s *Server) SendWhoList() { } // If this person had booted us, force their camera to "off" - if user.Boots(sub.Username) || user.Mutes(sub.Username) { + if (user.Boots(sub.Username) || user.Mutes(sub.Username)) && !sub.IsAdmin() { who.Video = 0 } diff --git a/web/static/js/BareRTC.js b/web/static/js/BareRTC.js index 815d7b5..7b9ec36 100644 --- a/web/static/js/BareRTC.js +++ b/web/static/js/BareRTC.js @@ -155,6 +155,7 @@ const app = Vue.createApp({ // Streams per username. streams: {}, muted: {}, // muted bool per username + booted: {}, // booted bool per username poppedOut: {}, // popped-out video per username // RTCPeerConnections per username. @@ -655,6 +656,13 @@ const app = Vue.createApp({ this.startWebRTC(msg.username, true); }, onRing(msg) { + // Admin moderation feature: if the user has booted an admin off their camera, do not + // notify if the admin re-opens their camera. + if (this.isBootedAdmin(msg.username)) { + this.startWebRTC(msg.username, false); + return; + } + this.ChatServer(`${msg.username} has opened your camera.`); this.startWebRTC(msg.username, false); }, @@ -1021,6 +1029,7 @@ const app = Vue.createApp({ }, onWatch(msg) { // The user has our video feed open now. + if (this.isBootedAdmin(msg.username)) return; this.webcam.watching[msg.username] = true; }, onUnwatch(msg) { @@ -1510,6 +1519,7 @@ const app = Vue.createApp({ } this.sendBoot(username); + this.WebRTC.booted[username] = true; // Close the WebRTC peer connection. if (this.WebRTC.pc[username] != undefined) { @@ -1523,6 +1533,11 @@ const app = Vue.createApp({ `in place for the remainder of your current chat session.` ); }, + isBootedAdmin(username) { + return (this.WebRTC.booted[username] === true || this.muted[username] === true) && + this.whoMap[username] != undefined && + this.whoMap[username].op; + }, // Stop broadcasting. stopVideo() {