Selaa lähdekoodia

Add support for protoXEP: MUC mention notifications

JC Brand 4 vuotta sitten
vanhempi
commit
62dbb1062f

+ 1 - 0
karma.conf.js

@@ -52,6 +52,7 @@ module.exports = function(config) {
       { pattern: "spec/markers.js", type: 'module' },
       { pattern: "spec/rai.js", type: 'module' },
       { pattern: "spec/muc_messages.js", type: 'module' },
+      { pattern: "spec/muc-mentions.js", type: 'module' },
       { pattern: "spec/me-messages.js", type: 'module' },
       { pattern: "spec/mentions.js", type: 'module' },
       { pattern: "spec/retractions.js", type: 'module' },

+ 88 - 0
spec/muc-mentions.js

@@ -0,0 +1,88 @@
+/*global mock, converse */
+
+const { Strophe, dayjs } = converse.env;
+const u = converse.env.utils;
+// See: https://xmpp.org/rfcs/rfc3921.html
+
+
+describe("MUC Mention Notfications", function () {
+
+    it("may be received from a MUC in which the user is not currently present",
+        mock.initConverse(
+            ['rosterGroupsFetched'], {
+                'allow_bookmarks': false, // Hack to get the rooms list to render
+                'muc_subscribe_to_rai': true,
+                'view_mode': 'fullscreen'},
+            async function (done, _converse) {
+
+        const { api } = _converse;
+
+        expect(_converse.session.get('rai_enabled_domains')).toBe(undefined);
+
+        const muc_jid = 'lounge@montague.lit';
+        const nick = 'romeo';
+        const muc_creation_promise = await api.rooms.open(muc_jid, {nick, 'hidden': true}, false);
+        await mock.getRoomFeatures(_converse, muc_jid, []);
+        await mock.receiveOwnMUCPresence(_converse, muc_jid, nick);
+        await muc_creation_promise;
+
+        const view = api.chatviews.get(muc_jid);
+        await u.waitUntil(() => (view.model.session.get('connection_status') === converse.ROOMSTATUS.ENTERED));
+        expect(view.model.get('hidden')).toBe(true);
+        await u.waitUntil(() => view.model.session.get('connection_status') === converse.ROOMSTATUS.DISCONNECTED);
+
+        const lview = _converse.rooms_list_view
+        const room_el = await u.waitUntil(() => lview.el.querySelector(".available-chatroom"));
+        expect(Array.from(room_el.classList).includes('unread-msgs')).toBeFalsy();
+
+        const base_time = new Date();
+        let message = u.toStanza(`
+            <message from="${Strophe.getDomainFromJid(muc_jid)}">
+                <mentions xmlns='urn:xmpp:mmn:0'>
+                    <forwarded xmlns='urn:xmpp:forward:0'>
+                        <delay xmlns='urn:xmpp:delay' stamp='${dayjs(base_time).subtract(5, 'minutes').toISOString()}'/>
+                        <message type='groupchat' id='${_converse.connection.getUniqueId()}'
+                            to='${muc_jid}'
+                            from='${muc_jid}/juliet'
+                            xml:lang='en'>
+                            <body>Romeo, wherefore art though Romeo</body>
+                            <reference xmlns='urn:xmpp:reference:0'
+                                type='mention'
+                                begin='0'
+                                uri='xmpp:${_converse.bare_jid}'
+                                end='5'/>
+                        </message>
+                    </forwarded>
+                </mentions>
+            </message>
+        `);
+        _converse.connection._dataRecv(mock.createRequest(message));
+        expect(Array.from(room_el.classList).includes('unread-msgs')).toBeTruthy();
+        expect(room_el.querySelector('.msgs-indicator')?.textContent.trim()).toBe('1');
+
+        message = u.toStanza(`
+            <message from="${Strophe.getDomainFromJid(muc_jid)}">
+                <mentions xmlns='urn:xmpp:mmn:0'>
+                    <forwarded xmlns='urn:xmpp:forward:0'>
+                        <delay xmlns='urn:xmpp:delay' stamp='${dayjs(base_time).subtract(4, 'minutes').toISOString()}'/>
+                        <message type='groupchat' id='${_converse.connection.getUniqueId()}'
+                            to='${muc_jid}'
+                            from='${muc_jid}/juliet'
+                            xml:lang='en'>
+                            <body>Romeo, wherefore art though Romeo</body>
+                            <reference xmlns='urn:xmpp:reference:0'
+                                type='mention'
+                                begin='0'
+                                uri='xmpp:${_converse.bare_jid}'
+                                end='5'/>
+                        </message>
+                    </forwarded>
+                </mentions>
+            </message>
+        `);
+        _converse.connection._dataRecv(mock.createRequest(message));
+        expect(Array.from(room_el.classList).includes('unread-msgs')).toBeTruthy();
+        expect(room_el.querySelector('.msgs-indicator')?.textContent.trim()).toBe('2');
+        done();
+    }));
+});

+ 1 - 0
src/headless/core.js

@@ -38,6 +38,7 @@ Strophe.addNamespace('HINTS', 'urn:xmpp:hints');
 Strophe.addNamespace('HTTPUPLOAD', 'urn:xmpp:http:upload:0');
 Strophe.addNamespace('IDLE', 'urn:xmpp:idle:1');
 Strophe.addNamespace('MAM', 'urn:xmpp:mam:2');
+Strophe.addNamespace('MENTIONS', 'urn:xmpp:mmn:0');
 Strophe.addNamespace('MODERATE', 'urn:xmpp:message-moderate:0');
 Strophe.addNamespace('NICK', 'http://jabber.org/protocol/nick');
 Strophe.addNamespace('OMEMO', 'eu.siacs.conversations.axolotl');

+ 19 - 2
src/headless/plugins/muc/muc.js

@@ -184,7 +184,7 @@ const ChatRoomMixin = {
      * @param { Boolean } force - Whether a marker should be sent for the
      *  message, even if it didn't include a `markable` element.
      */
-    sendMarkerForMessage (msg, type='displayed', force=false) {
+    sendMarkerForMessage (msg, type = 'displayed', force = false) {
         if (!msg) return;
         if (msg?.get('is_markable') || force) {
             const id = msg.get(`stanza_id ${this.get('jid')}`);
@@ -431,7 +431,6 @@ const ChatRoomMixin = {
         }
     },
 
-
     /**
      * Handles incoming message stanzas from the service that hosts this MUC
      * @private
@@ -447,6 +446,24 @@ const ChatRoomMixin = {
                 'num_unread_general': 0 // Either/or between activity and unreads
             });
         }
+
+        const msgs = sizzle(
+            `mentions[xmlns="${Strophe.NS.MENTIONS}"] forwarded[xmlns="${Strophe.NS.FORWARD}"] message[type="groupchat"]`,
+            stanza
+        );
+        const muc_jid = this.get('jid');
+        const mentions = msgs.filter(m => Strophe.getBareJidFromJid(m.getAttribute('from')) === muc_jid);
+        if (mentions.length) {
+            this.save({
+                'has_activity': true,
+                'num_unread': this.get('num_unread') + mentions.length
+            });
+            mentions.forEach(async stanza => {
+                const attrs = await parseMUCMessage(stanza, this, _converse);
+                const data = { stanza, attrs, 'chatbox': this };
+                api.trigger('message', data);
+            });
+        }
     },
 
     /**

+ 3 - 0
src/headless/shared/parsers.js

@@ -250,6 +250,9 @@ export function isHeadline (stanza) {
 }
 
 export function isServerMessage (stanza) {
+    if (sizzle(`mentions[xmlns="${Strophe.NS.MENTIONS}"]`, stanza).pop()) {
+        return false;
+    }
     const from_jid = stanza.getAttribute('from');
     if (stanza.getAttribute('type') !== 'error' && from_jid && !from_jid.includes('@')) {
         // Some servers (e.g. Prosody) don't set the stanza

+ 8 - 4
src/plugins/notifications.js

@@ -60,9 +60,9 @@ converse.plugins.add('converse-notification', {
                 return false;
             }
             const jid = attrs.from;
-            const room_jid = attrs.from_muc;
+            const muc_jid = attrs.from_muc;
             const notify_all = api.settings.get('notify_all_room_messages');
-            const room = _converse.chatboxes.get(room_jid);
+            const room = _converse.chatboxes.get(muc_jid);
             const resource = Strophe.getResourceFromJid(jid);
             const sender = resource && Strophe.unescapeNode(resource) || '';
             let is_mentioned = false;
@@ -72,10 +72,14 @@ converse.plugins.add('converse-notification', {
                 is_mentioned = (new RegExp(`\\b${nick}\\b`)).test(attrs.body);
             }
 
-            const is_referenced = attrs.references.map(r => r.value).includes(nick);
+            const references_me = (r) => {
+                const jid =  r.uri.replace(/^xmpp:/, '');
+                return jid == _converse.bare_jid || jid === `${muc_jid}/${nick}`;
+            }
+            const is_referenced = attrs.references.reduce((acc, r) => acc || references_me(r), false);
             const is_not_mine = sender !== nick;
             const should_notify_user = notify_all === true
-                || (Array.isArray(notify_all) && notify_all.includes(room_jid))
+                || (Array.isArray(notify_all) && notify_all.includes(muc_jid))
                 || is_referenced
                 || is_mentioned;
             return is_not_mine && !!should_notify_user;