Mercurial > prosody-modules
changeset 4243:aed7038ab2ab
mod_muc_inject_mentions: Make module scalable by iterating through the body instead of participants list as the main loop
author | Seve Ferrer <seve@delape.net> |
---|---|
date | Sat, 14 Nov 2020 18:02:49 +0100 (2020-11-14) |
parents | 6a91d217acc9 |
children | 07c11080027e |
files | mod_muc_inject_mentions/mod_muc_inject_mentions.lua mod_muc_inject_mentions/spec/scansion/mod_muc_inject_mentions.scs |
diffstat | 2 files changed, 183 insertions(+), 56 deletions(-) [+] |
line wrap: on
line diff
--- a/mod_muc_inject_mentions/mod_muc_inject_mentions.lua Thu Nov 12 20:37:54 2020 +0100 +++ b/mod_muc_inject_mentions/mod_muc_inject_mentions.lua Sat Nov 14 18:02:49 2020 +0100 @@ -3,18 +3,63 @@ local jid_resource = require "util.jid".resource; local st = require "util.stanza"; -local prefixes = module:get_option("muc_inject_mentions_prefixes", nil) -local suffixes = module:get_option("muc_inject_mentions_suffixes", nil) +local prefixes = module:get_option_set("muc_inject_mentions_prefixes", {}) +local suffixes = module:get_option_set("muc_inject_mentions_suffixes", {}) local enabled_rooms = module:get_option("muc_inject_mentions_enabled_rooms", nil) local disabled_rooms = module:get_option("muc_inject_mentions_disabled_rooms", nil) -local mention_delimiters = module:get_option_set("muc_inject_mentions_mention_delimiters", {" ", "", "\n"}) +local mention_delimiters = module:get_option_set("muc_inject_mentions_mention_delimiters", {" ", "", "\n", "\t"}) local append_mentions = module:get_option("muc_inject_mentions_append_mentions", false) local strip_out_prefixes = module:get_option("muc_inject_mentions_strip_out_prefixes", false) local reserved_nicks = module:get_option("muc_inject_mentions_reserved_nicks", false) - +local use_real_jid = module:get_option("muc_inject_mentions_use_real_jid", false) +local reserved_nicknames = {} local reference_xmlns = "urn:xmpp:reference:0" +local function update_reserved_nicknames(event) + local room, data, jid = event.room.jid, event.data, event.jid + load_room_reserved_nicknames(event.room) + local nickname = (data or {})["reserved_nickname"] + + if nickname then + reserved_nicknames[room][nickname] = jid + else + local nickname_to_remove + for _nickname, _jid in pairs(reserved_nicknames[room]) do + if _jid == jid then + nickname_to_remove = _nickname + break + end + end + if nickname_to_remove then + reserved_nicknames[room][nickname_to_remove] = nil + end + end +end + +function load_room_reserved_nicknames(room) + if not reserved_nicknames[room.jid] then + reserved_nicknames[room.jid] = {} + for jid, data in pairs(room._affiliations_data or {}) do + local reserved_nickname = data["reserved_nickname"] + if reserved_nicknames then + reserved_nicknames[room.jid][reserved_nickname] = jid + end + end + end +end + +local function get_jid(room, nickname) + local real_jid = reserved_nicknames[room.jid][nickname] + if real_jid and use_real_jid then + return real_jid + end + + if real_jid and not use_real_jid then + return room.jid .. "/" .. nickname + end +end + local function get_participants(room) if not reserved_nicks then local occupants = room._occupants @@ -137,55 +182,45 @@ end local function search_mentions(room, body, client_mentions) + load_room_reserved_nicknames(room) local mentions, prefix_indices = {}, {} - - for bare_jid, nick in get_participants(room) do - -- Check for multiple mentions to the same nickname in a message - -- Hey @nick remember to... Ah, also @nick please let me know if... - local matches = {} - local _first - local _last = 0 - while true do - -- Use plain search as nick could contain - -- characters used in Lua patterns - _first, _last = body:find(nick, _last + 1, true) - if _first == nil then break end - table.insert(matches, {first=_first, last=_last}) - end - - -- Filter out intentional mentions from unintentional ones - for _, match in ipairs(matches) do - local first, last = match.first, match.last - -- Only append new mentions in case the client already sent some - if not client_mentions[first] then - -- Body only contains nickname or is between spaces, new lines or at the end/start of the body - if mention_delimiters:contains(body:sub(first - 1, first - 1)) and - mention_delimiters:contains(body:sub(last + 1, last + 1)) - then - add_mention(mentions, bare_jid, first, last, prefix_indices, false) - else - -- Check if occupant is mentioned using affixes - local has_prefix = has_nick_prefix(body, first) - local has_suffix = has_nick_suffix(body, last) - - -- @nickname: ... - if has_prefix and has_suffix then - add_mention(mentions, bare_jid, first, last, prefix_indices, has_prefix) - - -- @nickname ... - elseif has_prefix and not has_suffix then - if mention_delimiters:contains(body:sub(last + 1, last + 1)) then - add_mention(mentions, bare_jid, first, last, prefix_indices, has_prefix) - end - - -- nickname: ... - elseif not has_prefix and has_suffix then - if mention_delimiters:contains(body:sub(first - 1, first - 1)) then - add_mention(mentions, bare_jid, first, last, prefix_indices, has_prefix) - end + local current_word = "" + local current_word_start + for i = 1, #body+1 do + local char = body:sub(i,i) + -- Mention delimiter found, current_word is completed now + if mention_delimiters:contains(char) and current_word_start then + -- Check for nickname without prefix + local jid = get_jid(room, current_word) + if jid then + add_mention(mentions, jid, current_word_start, i - 1, prefix_indices, false) + else + -- Check for nickname with affixes + local prefix = prefixes:contains(current_word:sub(1,1)) + local suffix = suffixes:contains(current_word:sub(-1)) + if prefix and suffix then + jid = get_jid(room, current_word:sub(2, -2)) + if jid then + add_mention(mentions, jid, current_word_start + 1, i - 2, prefix_indices, true) + end + elseif prefix then + jid = get_jid(room, current_word:sub(2)) + if jid then + add_mention(mentions, jid, current_word_start + 1, i - 1, prefix_indices, true) + end + elseif suffix then + jid = get_jid(room, current_word:sub(1, -2)) + if jid then + add_mention(mentions, jid, current_word_start, i - 2, prefix_indices, false) end end end + + current_word = "" + current_word_start = nil + elseif not mention_delimiters:contains(char) then + current_word_start = current_word_start or i + current_word = current_word .. char end end @@ -194,9 +229,9 @@ local function muc_inject_mentions(event) local room, stanza = event.room, event.stanza; - local body = stanza:get_child("body") + local body = stanza:get_child_text("body") - if not body then return; end + if not body or #body < 1 then return; end -- Inject mentions only if the room is configured for them if not is_room_eligible(room.jid) then return; end @@ -206,8 +241,7 @@ local has_mentions, client_mentions = get_client_mentions(stanza) if has_mentions and not append_mentions then return; end - local body_text = body:get_text() - local mentions, prefix_indices = search_mentions(room, body_text, client_mentions) + local mentions, prefix_indices = search_mentions(room, body, client_mentions) for _, mention in pairs(mentions) do -- https://xmpp.org/extensions/xep-0372.html#usecase_mention stanza:tag( @@ -226,10 +260,10 @@ local from = 0 if #prefix_indices > 0 then for _, prefix_index in ipairs(prefix_indices) do - body_without_prefixes = body_without_prefixes .. body_text:sub(from, prefix_index-1) + body_without_prefixes = body_without_prefixes .. body:sub(from, prefix_index-1) from = prefix_index + 1 end - body_without_prefixes = body_without_prefixes .. body_text:sub(from, #body_text) + body_without_prefixes = body_without_prefixes .. body:sub(from, #body) -- Replace original body containing prefixes stanza:maptags( @@ -244,4 +278,5 @@ end end -module:hook("muc-occupant-groupchat", muc_inject_mentions) \ No newline at end of file +module:hook("muc-occupant-groupchat", muc_inject_mentions) +module:hook("muc-set-affiliation", update_reserved_nicknames)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_muc_inject_mentions/spec/scansion/mod_muc_inject_mentions.scs Sat Nov 14 18:02:49 2020 +0100 @@ -0,0 +1,92 @@ +# mod_muc_inject_mentions + +[Client] Romeo + jid: romeo@example.org + password: 1234 + +----- + +Romeo connects + +Romeo sends: + <presence to='room@chat.example.org/Romeo'> + <x xmlns='http://jabber.org/protocol/muc'/> + </presence> + +Romeo receives: + <presence from='room@chat.example.org/Romeo'> + <x xmlns='vcard-temp:x:update'> + <photo xmlns='vcard-temp:x:update'/> + </x> + <x xmlns='http://jabber.org/protocol/muc#user'> + <status code='201'/> + <item jid="${Romeo's full JID}" affiliation='owner' role='moderator'/> + <status code='110'/> + </x> + </presence> + +Romeo sends: + <iq from="${Romeo's full JID}" + id='muc_register1' + to='room@chat.example.org' + type='set'> + <query xmlns='jabber:iq:register'> + <x xmlns='jabber:x:data' type='submit'> + <field var='FORM_TYPE'> + <value>http://jabber.org/protocol/muc#register</value> + </field> + <field var='muc#register_roomnick'> + <value>Romeo</value> + </field> + </x> + </query> + </iq> + +Romeo receives: +# <iq from='room@chat.example.org' +# id='muc_register1' +# to="{Romeo's full JID}" +# type='result'/> + <presence from='room@chat.example.org/Romeo' to="${Romeo's full JID}"> + <x xmlns='vcard-temp:x:update'> + <photo xmlns='vcard-temp:x:update'/> + </x> + <x xmlns='http://jabber.org/protocol/muc#user'> + <item affiliation='owner' xmlns='http://jabber.org/protocol/muc#user' role='moderator' jid="${Romeo's full JID}"/> + <status code='110' xmlns='http://jabber.org/protocol/muc#user'/> + </x> + </presence> + + +Romeo sends: + <message from="${Romeo's full JID}" id='mentions1' to='room@chat.example.org' type='groupchat'> + <body> + Hey Romeo how are you doing Romeo? Good To see you Romeo! + Romeo is very nice! + So Romeo is Romeo and I am not Romeo + Romeo! + !Romeo + Romeo + Haha !Romeo + @Romeo haha + You are awesome Romeo! + </body> + </message> + +Romeo receives: + <message to="${Romeo's full JID}" id='mentions1' type='groupchat' from='room@chat.example.org/Romeo'> + <body> + Hey Romeo how are you doing Romeo? Good To see you Romeo! Romeo is very nice! So Romeo is Romeo and I am not Romeo Romeo! !Romeo Romeo Haha !Romeo @Romeo haha You are awesome Romeo! + </body> + <reference begin='8' end='12' xmlns='urn:xmpp:reference:0' type='mention' uri='xmpp:romeo@example.org'/> + <reference begin='32' end='36' xmlns='urn:xmpp:reference:0' type='mention' uri='xmpp:romeo@example.org'/> + <reference begin='55' end='59' xmlns='urn:xmpp:reference:0' type='mention' uri='xmpp:romeo@example.org'/> + <reference begin='65' end='69' xmlns='urn:xmpp:reference:0' type='mention' uri='xmpp:romeo@example.org'/> + <reference begin='91' end='95' xmlns='urn:xmpp:reference:0' type='mention' uri='xmpp:romeo@example.org'/> + <reference begin='100' end='104' xmlns='urn:xmpp:reference:0' type='mention' uri='xmpp:romeo@example.org'/> + <reference begin='119' end='123' xmlns='urn:xmpp:reference:0' type='mention' uri='xmpp:romeo@example.org'/> + <reference begin='128' end='132' xmlns='urn:xmpp:reference:0' type='mention' uri='xmpp:romeo@example.org'/> + <reference begin='148' end='152' xmlns='urn:xmpp:reference:0' type='mention' uri='xmpp:romeo@example.org'/> + <reference begin='173' end='177' xmlns='urn:xmpp:reference:0' type='mention' uri='xmpp:romeo@example.org'/> + <reference begin='203' end='207' xmlns='urn:xmpp:reference:0' type='mention' uri='xmpp:romeo@example.org'/> + </message>