comparison mod_muc_inject_mentions/mod_muc_inject_mentions.lua @ 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
parents a82b0745383b
children 32b4901a9d8d
comparison
equal deleted inserted replaced
4242:6a91d217acc9 4243:aed7038ab2ab
1 module:depends("muc"); 1 module:depends("muc");
2 2
3 local jid_resource = require "util.jid".resource; 3 local jid_resource = require "util.jid".resource;
4 local st = require "util.stanza"; 4 local st = require "util.stanza";
5 5
6 local prefixes = module:get_option("muc_inject_mentions_prefixes", nil) 6 local prefixes = module:get_option_set("muc_inject_mentions_prefixes", {})
7 local suffixes = module:get_option("muc_inject_mentions_suffixes", nil) 7 local suffixes = module:get_option_set("muc_inject_mentions_suffixes", {})
8 local enabled_rooms = module:get_option("muc_inject_mentions_enabled_rooms", nil) 8 local enabled_rooms = module:get_option("muc_inject_mentions_enabled_rooms", nil)
9 local disabled_rooms = module:get_option("muc_inject_mentions_disabled_rooms", nil) 9 local disabled_rooms = module:get_option("muc_inject_mentions_disabled_rooms", nil)
10 local mention_delimiters = module:get_option_set("muc_inject_mentions_mention_delimiters", {" ", "", "\n"}) 10 local mention_delimiters = module:get_option_set("muc_inject_mentions_mention_delimiters", {" ", "", "\n", "\t"})
11 local append_mentions = module:get_option("muc_inject_mentions_append_mentions", false) 11 local append_mentions = module:get_option("muc_inject_mentions_append_mentions", false)
12 local strip_out_prefixes = module:get_option("muc_inject_mentions_strip_out_prefixes", false) 12 local strip_out_prefixes = module:get_option("muc_inject_mentions_strip_out_prefixes", false)
13 local reserved_nicks = module:get_option("muc_inject_mentions_reserved_nicks", false) 13 local reserved_nicks = module:get_option("muc_inject_mentions_reserved_nicks", false)
14 14 local use_real_jid = module:get_option("muc_inject_mentions_use_real_jid", false)
15 local reserved_nicknames = {}
15 16
16 local reference_xmlns = "urn:xmpp:reference:0" 17 local reference_xmlns = "urn:xmpp:reference:0"
18
19 local function update_reserved_nicknames(event)
20 local room, data, jid = event.room.jid, event.data, event.jid
21 load_room_reserved_nicknames(event.room)
22 local nickname = (data or {})["reserved_nickname"]
23
24 if nickname then
25 reserved_nicknames[room][nickname] = jid
26 else
27 local nickname_to_remove
28 for _nickname, _jid in pairs(reserved_nicknames[room]) do
29 if _jid == jid then
30 nickname_to_remove = _nickname
31 break
32 end
33 end
34 if nickname_to_remove then
35 reserved_nicknames[room][nickname_to_remove] = nil
36 end
37 end
38 end
39
40 function load_room_reserved_nicknames(room)
41 if not reserved_nicknames[room.jid] then
42 reserved_nicknames[room.jid] = {}
43 for jid, data in pairs(room._affiliations_data or {}) do
44 local reserved_nickname = data["reserved_nickname"]
45 if reserved_nicknames then
46 reserved_nicknames[room.jid][reserved_nickname] = jid
47 end
48 end
49 end
50 end
51
52 local function get_jid(room, nickname)
53 local real_jid = reserved_nicknames[room.jid][nickname]
54 if real_jid and use_real_jid then
55 return real_jid
56 end
57
58 if real_jid and not use_real_jid then
59 return room.jid .. "/" .. nickname
60 end
61 end
17 62
18 local function get_participants(room) 63 local function get_participants(room)
19 if not reserved_nicks then 64 if not reserved_nicks then
20 local occupants = room._occupants 65 local occupants = room._occupants
21 local key, occupant = next(occupants) 66 local key, occupant = next(occupants)
135 180
136 return false 181 return false
137 end 182 end
138 183
139 local function search_mentions(room, body, client_mentions) 184 local function search_mentions(room, body, client_mentions)
185 load_room_reserved_nicknames(room)
140 local mentions, prefix_indices = {}, {} 186 local mentions, prefix_indices = {}, {}
141 187 local current_word = ""
142 for bare_jid, nick in get_participants(room) do 188 local current_word_start
143 -- Check for multiple mentions to the same nickname in a message 189 for i = 1, #body+1 do
144 -- Hey @nick remember to... Ah, also @nick please let me know if... 190 local char = body:sub(i,i)
145 local matches = {} 191 -- Mention delimiter found, current_word is completed now
146 local _first 192 if mention_delimiters:contains(char) and current_word_start then
147 local _last = 0 193 -- Check for nickname without prefix
148 while true do 194 local jid = get_jid(room, current_word)
149 -- Use plain search as nick could contain 195 if jid then
150 -- characters used in Lua patterns 196 add_mention(mentions, jid, current_word_start, i - 1, prefix_indices, false)
151 _first, _last = body:find(nick, _last + 1, true) 197 else
152 if _first == nil then break end 198 -- Check for nickname with affixes
153 table.insert(matches, {first=_first, last=_last}) 199 local prefix = prefixes:contains(current_word:sub(1,1))
154 end 200 local suffix = suffixes:contains(current_word:sub(-1))
155 201 if prefix and suffix then
156 -- Filter out intentional mentions from unintentional ones 202 jid = get_jid(room, current_word:sub(2, -2))
157 for _, match in ipairs(matches) do 203 if jid then
158 local first, last = match.first, match.last 204 add_mention(mentions, jid, current_word_start + 1, i - 2, prefix_indices, true)
159 -- Only append new mentions in case the client already sent some 205 end
160 if not client_mentions[first] then 206 elseif prefix then
161 -- Body only contains nickname or is between spaces, new lines or at the end/start of the body 207 jid = get_jid(room, current_word:sub(2))
162 if mention_delimiters:contains(body:sub(first - 1, first - 1)) and 208 if jid then
163 mention_delimiters:contains(body:sub(last + 1, last + 1)) 209 add_mention(mentions, jid, current_word_start + 1, i - 1, prefix_indices, true)
164 then 210 end
165 add_mention(mentions, bare_jid, first, last, prefix_indices, false) 211 elseif suffix then
166 else 212 jid = get_jid(room, current_word:sub(1, -2))
167 -- Check if occupant is mentioned using affixes 213 if jid then
168 local has_prefix = has_nick_prefix(body, first) 214 add_mention(mentions, jid, current_word_start, i - 2, prefix_indices, false)
169 local has_suffix = has_nick_suffix(body, last)
170
171 -- @nickname: ...
172 if has_prefix and has_suffix then
173 add_mention(mentions, bare_jid, first, last, prefix_indices, has_prefix)
174
175 -- @nickname ...
176 elseif has_prefix and not has_suffix then
177 if mention_delimiters:contains(body:sub(last + 1, last + 1)) then
178 add_mention(mentions, bare_jid, first, last, prefix_indices, has_prefix)
179 end
180
181 -- nickname: ...
182 elseif not has_prefix and has_suffix then
183 if mention_delimiters:contains(body:sub(first - 1, first - 1)) then
184 add_mention(mentions, bare_jid, first, last, prefix_indices, has_prefix)
185 end
186 end 215 end
187 end 216 end
188 end 217 end
218
219 current_word = ""
220 current_word_start = nil
221 elseif not mention_delimiters:contains(char) then
222 current_word_start = current_word_start or i
223 current_word = current_word .. char
189 end 224 end
190 end 225 end
191 226
192 return mentions, prefix_indices 227 return mentions, prefix_indices
193 end 228 end
194 229
195 local function muc_inject_mentions(event) 230 local function muc_inject_mentions(event)
196 local room, stanza = event.room, event.stanza; 231 local room, stanza = event.room, event.stanza;
197 local body = stanza:get_child("body") 232 local body = stanza:get_child_text("body")
198 233
199 if not body then return; end 234 if not body or #body < 1 then return; end
200 235
201 -- Inject mentions only if the room is configured for them 236 -- Inject mentions only if the room is configured for them
202 if not is_room_eligible(room.jid) then return; end 237 if not is_room_eligible(room.jid) then return; end
203 238
204 -- Only act on messages that do not include mentions 239 -- Only act on messages that do not include mentions
205 -- unless configuration states otherwise. 240 -- unless configuration states otherwise.
206 local has_mentions, client_mentions = get_client_mentions(stanza) 241 local has_mentions, client_mentions = get_client_mentions(stanza)
207 if has_mentions and not append_mentions then return; end 242 if has_mentions and not append_mentions then return; end
208 243
209 local body_text = body:get_text() 244 local mentions, prefix_indices = search_mentions(room, body, client_mentions)
210 local mentions, prefix_indices = search_mentions(room, body_text, client_mentions)
211 for _, mention in pairs(mentions) do 245 for _, mention in pairs(mentions) do
212 -- https://xmpp.org/extensions/xep-0372.html#usecase_mention 246 -- https://xmpp.org/extensions/xep-0372.html#usecase_mention
213 stanza:tag( 247 stanza:tag(
214 "reference", { 248 "reference", {
215 xmlns=reference_xmlns, 249 xmlns=reference_xmlns,
224 if strip_out_prefixes then 258 if strip_out_prefixes then
225 local body_without_prefixes = "" 259 local body_without_prefixes = ""
226 local from = 0 260 local from = 0
227 if #prefix_indices > 0 then 261 if #prefix_indices > 0 then
228 for _, prefix_index in ipairs(prefix_indices) do 262 for _, prefix_index in ipairs(prefix_indices) do
229 body_without_prefixes = body_without_prefixes .. body_text:sub(from, prefix_index-1) 263 body_without_prefixes = body_without_prefixes .. body:sub(from, prefix_index-1)
230 from = prefix_index + 1 264 from = prefix_index + 1
231 end 265 end
232 body_without_prefixes = body_without_prefixes .. body_text:sub(from, #body_text) 266 body_without_prefixes = body_without_prefixes .. body:sub(from, #body)
233 267
234 -- Replace original body containing prefixes 268 -- Replace original body containing prefixes
235 stanza:maptags( 269 stanza:maptags(
236 function(tag) 270 function(tag)
237 if tag.name ~= "body" then 271 if tag.name ~= "body" then
243 end 277 end
244 end 278 end
245 end 279 end
246 280
247 module:hook("muc-occupant-groupchat", muc_inject_mentions) 281 module:hook("muc-occupant-groupchat", muc_inject_mentions)
282 module:hook("muc-set-affiliation", update_reserved_nicknames)