From 59fc04b99e0df23f390e3af1f1bd8109c48227ec Mon Sep 17 00:00:00 2001 From: Noah Petherbridge Date: Mon, 28 Aug 2023 22:46:40 -0700 Subject: [PATCH] Reduce Notification Spam; Unsolicited DMs Option; New SFX * New "Misc" tab added to the Settings modal with options to reduce spam and improve privacy. * Opt in (or out) for public channel join/leave presence notifications * New option to auto-ignore unsolicited DMs * New sound effects for Watched and Unwatched (your camera) * Reduces spam so ChatServer doesn't need to tell you every time somebody opens your camera. * New spinner icon when opening someone else's camera. * If their cam takes a while to appear, the video button shows a spinner icon as feedback so we avoid ChatClient spam giving you acknowledgement of the cam trying to open. --- web/static/js/BareRTC.js | 102 ++++++++++++++++++---- web/static/js/sounds.js | 14 ++- web/static/sfx/notification-6175-down.mp3 | Bin 0 -> 16748 bytes web/static/sfx/notification-6175-up.mp3 | Bin 0 -> 16748 bytes web/templates/chat.html | 97 ++++++++++++++++++-- 5 files changed, 186 insertions(+), 27 deletions(-) create mode 100644 web/static/sfx/notification-6175-down.mp3 create mode 100644 web/static/sfx/notification-6175-up.mp3 diff --git a/web/static/js/BareRTC.js b/web/static/js/BareRTC.js index 1ad48b2..c672fe2 100644 --- a/web/static/js/BareRTC.js +++ b/web/static/js/BareRTC.js @@ -123,6 +123,13 @@ const app = Vue.createApp({ whoMap: {}, // map username to wholist entry muted: {}, // muted usernames for client side state + // Misc. user preferences (TODO: move all of them here) + prefs: { + joinMessages: true, // show "has entered the room" in public channels + exitMessages: false, // hide exit messages by default in public channels + closeDMs: false, // ignore unsolicited DMs + }, + // My video feed. webcam: { busy: false, @@ -192,6 +199,11 @@ const app = Vue.createApp({ // Debounce connection attempts since now every click = try to connect. debounceOpens: {}, // map usernames to bools + + // Timeouts for open camera attempts. e.g.: when you click to view + // a camera, the icon changes to a spinner for a few seconds to see + // whether the video goes on to open. + openTimeouts: {}, // map usernames to timeouts }, // Chat history. @@ -367,6 +379,17 @@ const app = Vue.createApp({ this.sendMe(); } }, + + // Misc preference watches + "prefs.joinMessages": function() { + localStorage.joinMessages = this.prefs.joinMessages; + }, + "prefs.exitMessages": function() { + localStorage.exitMessages = this.prefs.exitMessages; + }, + "prefs.closeDMs": function() { + localStorage.closeDMs = this.prefs.closeDMs; + }, }, computed: { chatHistory() { @@ -557,6 +580,17 @@ const app = Vue.createApp({ if (localStorage.videoAutoMute === "true") { this.webcam.autoMute = true; } + + // Misc preferences + if (localStorage.joinMessages != undefined) { + this.prefs.joinMessages = localStorage.joinMessages === "true"; + } + if (localStorage.exitMessages != undefined) { + this.prefs.exitMessages = localStorage.exitMessages === "true"; + } + if (localStorage.closeDMs != undefined) { + this.prefs.closeDMs = localStorage.closeDMs === "true"; + } }, signIn() { @@ -815,14 +849,7 @@ 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.`); + // Request from a viewer to see our broadcast. this.startWebRTC(msg.username, false); }, onUserExited(msg) { @@ -835,7 +862,12 @@ const app = Vue.createApp({ // Play sound effects if this is not the active channel or the window is not focused. if (msg.channel.indexOf("@") === 0) { if (msg.channel !== this.channel || !this.windowFocused) { - this.playSound("DM"); + // If we are ignoring unsolicited DMs, don't play the sound effect here. + if (this.prefs.closeDMs && this.channels[msg.channel] == undefined) { + console.log("Unsolicited DM received"); + } else { + this.playSound("DM"); + } } } else if (msg.channel !== this.channel || !this.windowFocused) { this.playSound("Chat"); @@ -868,18 +900,20 @@ const app = Vue.createApp({ // User logged in or out. onPresence(msg) { // TODO: make a dedicated leave event - let isLeave = false; + let isLeave = false, + isJoin = false; if (msg.message.indexOf("has exited the room!") > -1) { // Clean up data about this user. this.onUserExited(msg); this.playSound("Leave"); isLeave = true; - } else { + } else if (msg.message.indexOf("has joined the room!") > -1) { this.playSound("Enter"); + isJoin = true; } - // Push it to the history of all public channels (not leaves). - if (!isLeave) { + // Push it to the history of all public channels (depending on user preference). + if ((isJoin && this.prefs.joinMessages) || (isLeave && this.prefs.exitMessages)) { for (let channel of this.config.channels) { this.pushHistory({ channel: channel.ID, @@ -1078,6 +1112,13 @@ const app = Vue.createApp({ pc.ontrack = event => { const stream = event.streams[0]; + // We've received a video! If we had an "open camera spinner timeout", + // clear it before it expires. + if (this.WebRTC.openTimeouts[username] != undefined) { + clearTimeout(this.WebRTC.openTimeouts[username]); + delete(this.WebRTC.openTimeouts[username]); + } + // Do we already have it? // this.ChatClient(`Received a video stream from ${username}.`); if (this.WebRTC.streams[username] == undefined || @@ -1212,10 +1253,12 @@ const app = Vue.createApp({ // The user has our video feed open now. if (this.isBootedAdmin(msg.username)) return; this.webcam.watching[msg.username] = true; + this.playSound("Watch"); }, onUnwatch(msg) { // The user has closed our video feed. delete(this.webcam.watching[msg.username]); + this.playSound("Unwatch"); }, sendWatch(username, watching) { // Send the watch or unwatch message to backend. @@ -1681,15 +1724,26 @@ const app = Vue.createApp({ return; } + // Set a timeout: the video icon becomes a spinner and we wait a while + // to see if the connection went thru. This gives the user feedback and we + // can avoid a spammy 'ChatClient' notification message. + if (this.WebRTC.openTimeouts[user.username] != undefined) { + clearTimeout(this.WebRTC.openTimeouts[user.username]); + delete(this.WebRTC.openTimeouts[user.username]); + } + this.WebRTC.openTimeouts[user.username] = setTimeout(() => { + // It timed out. + this.ChatClient( + `There was an error opening ${user.username}'s camera.`, + ); + delete(this.WebRTC.openTimeouts[user.username]); + }, 10000); + + // Send the ChatServer 'open' command. this.sendOpen(user.username); // Responsive CSS -> go to chat panel to see the camera this.openChatPanel(); - - // Send some feedback to the chat window. - this.ChatClient( - `A request was sent to open ${user.username}'s camera which should (hopefully) appear on your screen soon.`, - ); }, closeVideo(username, name) { // Clean up any lingering camera freeze states. @@ -1762,10 +1816,16 @@ const app = Vue.createApp({ // - Usually a video icon // - May be a crossed-out video if isVideoNotAllowed // - Or an eyeball for cameras already opened + // - Or a spinner if we are actively trying to open the video if (user.username === this.username && this.webcam.active) { return 'fa-eye'; // user sees their own self camera always } + // In spinner mode? (Trying to open the video) + if (this.WebRTC.openTimeouts[user.username] != undefined) { + return 'fa-spinner fa-spin'; + } + // Already opened? if (this.WebRTC.pc[user.username] != undefined && this.WebRTC.streams[user.username] != undefined) { return 'fa-eye'; @@ -2016,6 +2076,12 @@ const app = Vue.createApp({ channel = this.channel; } + // Are we ignoring DMs? + if (this.prefs.closeDMs && channel.indexOf('@') === 0) { + // Don't allow an (incoming) DM to initialize a new chat room for us. + if (username !== this.username && this.channels[channel] == undefined) return; + } + // Initialize this channel's history? this.initHistory(channel); diff --git a/web/static/js/sounds.js b/web/static/js/sounds.js index f035e89..1fa2c42 100644 --- a/web/static/js/sounds.js +++ b/web/static/js/sounds.js @@ -23,7 +23,15 @@ const SoundEffects = [ { name: "Sonar", filename: "sonar-ping-95840.mp3" - } + }, + { + name: "Up Chime", + filename: "notification-6175-up.mp3" + }, + { + name: "Down Chime", + filename: "notification-6175-down.mp3" + }, ]; // Defaults @@ -32,4 +40,6 @@ var DefaultSounds = { DM: "Trill", Enter: "Quiet", Leave: "Quiet", -}; \ No newline at end of file + Watch: "Up Chime", + Unwatch: "Quiet", +}; diff --git a/web/static/sfx/notification-6175-down.mp3 b/web/static/sfx/notification-6175-down.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..00a3fd9c7060b06cf9e6853148dec367090f1a15 GIT binary patch literal 16748 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 literal 0 HcmV?d00001 diff --git a/web/static/sfx/notification-6175-up.mp3 b/web/static/sfx/notification-6175-up.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..84d6f27680f915bdf589d7340b9bed86c6714f54 GIT binary patch literal 16748 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 literal 0 HcmV?d00001 diff --git a/web/templates/chat.html b/web/templates/chat.html index 0e25ad8..c76ab3f 100644 --- a/web/templates/chat.html +++ b/web/templates/chat.html @@ -90,6 +90,11 @@ Camera +
  • + + Misc + +
  • @@ -181,7 +186,7 @@
    - +
    @@ -196,7 +201,7 @@
    - +
    @@ -213,7 +218,7 @@
    - +
    @@ -228,7 +233,7 @@
    - +
    @@ -242,6 +247,38 @@
    + +
    +
    + +
    +
    +
    + +
    +
    + +
    + +
    +
    +
    + +
    +
    +
    @@ -332,6 +369,52 @@
    + +
    + +
    + +
    +
    + +
    + +
    + +
    +
    +

    + Whether to show 'has joined the room' style messages in public channels. +

    +
    + +
    + + +

    + If you check this box, other chatters may not initiate DMs with you: their messages + will be (silently) ignored. You may still initiate DM chats with others, unless they + also have closed their DMs with this setting. +

    +
    + +
    +