Comparison

mod_muc_inject_mentions/mod_muc_inject_mentions.lua @ 4138:e8c1b35bc25b

mod_muc_inject_mentions: Publish module to repository
author Seve Ferrer <seve@delape.net>
date Sun, 20 Sep 2020 10:31:02 +0200
child 4139:c6bb64a12f92
comparison
equal deleted inserted replaced
4137:5f4bcaad18ee 4138:e8c1b35bc25b
1 module:depends("muc");
2
3 local jid_split = require "util.jid".split;
4
5 local prefixes = module:get_option("muc_inject_mentions_prefixes", nil)
6 local suffixes = module:get_option("muc_inject_mentions_suffixes", nil)
7 local enabled_rooms = module:get_option("muc_inject_mentions_enabled_rooms", nil)
8 local disabled_rooms = module:get_option("muc_inject_mentions_disabled_rooms", nil)
9
10 local reference_xmlns = "urn:xmpp:reference:0"
11
12 local function is_room_eligible(jid)
13 if not enabled_rooms and not disabled_rooms then
14 return true;
15 end
16
17 if enabled_rooms and not disabled_rooms then
18 for _, _jid in ipairs(enabled_rooms) do
19 if _jid == jid then
20 return true
21 end
22 end
23 return false
24 end
25
26 if disabled_rooms and not enabled_rooms then
27 for _, _jid in ipairs(disabled_rooms) do
28 if _jid == jid then
29 return false
30 end
31 end
32 return true
33 end
34
35 return true
36 end
37
38 local function has_nick_prefix(body, first)
39 -- There is no prefix
40 -- but mention could still be valid
41 if first == 1 then return true end
42
43 -- There are no configured prefixes
44 if not prefixes or #prefixes < 1 then return false end
45
46 -- Preffix must have a space before it
47 -- or be the first character of the body
48 if body:sub(first - 2, first - 2) ~= "" and
49 body:sub(first - 2, first - 2) ~= " "
50 then
51 return false
52 end
53
54 local preffix = body:sub(first - 1, first - 1)
55 for i, _preffix in ipairs(prefixes) do
56 if preffix == _preffix then
57 return true
58 end
59 end
60
61 return false
62 end
63
64 local function has_nick_suffix(body, last)
65 -- There is no suffix
66 -- but mention could still be valid
67 if last == #body then return true end
68
69 -- There are no configured suffixes
70 if not suffixes or #suffixes < 1 then return false end
71
72 -- Suffix must have a space after it
73 -- or be the last character of the body
74 if body:sub(last + 2, last + 2) ~= "" and
75 body:sub(last + 2, last + 2) ~= " "
76 then
77 return false
78 end
79
80 local suffix = body:sub(last+1, last+1)
81 for i, _suffix in ipairs(suffixes) do
82 if suffix == _suffix then
83 return true
84 end
85 end
86
87 return false
88 end
89
90 local function search_mentions(room, stanza)
91 local body = stanza:get_child("body"):get_text();
92 local mentions = {}
93
94 for _, occupant in pairs(room._occupants) do
95 local node, host, nick = jid_split(occupant.nick);
96 -- Check for multiple mentions to the same nickname in a message
97 -- Hey @nick remember to... Ah, also @nick please let me know if...
98 local matches = {}
99 local _first, _last = 0, 0
100 while true do
101 -- Use plain search as nick could contain
102 -- characters used in Lua patterns
103 _first, _last = body:find(nick, _last + 1, true)
104 if _first == nil then break end
105 table.insert(matches, {first=_first, last=_last})
106 end
107
108 -- Filter out intentional mentions from unintentional ones
109 for _, match in ipairs(matches) do
110 local bare_jid = occupant.bare_jid
111 local first, last = match.first, match.last
112
113 -- Body only contains nickname
114 if first == 1 and last == #body then
115 table.insert(mentions, {bare_jid=bare_jid, first=first, last=last})
116
117 -- Nickname between spaces
118 elseif body:sub(first - 1, first - 1) == " " and
119 body:sub(last + 1, last + 1) == " "
120 then
121 table.insert(mentions, {bare_jid=bare_jid, first=first, last=last})
122 else
123 -- Check if occupant is mentioned using affixes
124 local has_preffix = has_nick_prefix(body, first)
125 local has_suffix = has_nick_suffix(body, last)
126
127 -- @nickname: ...
128 if has_preffix and has_suffix then
129 table.insert(mentions, {bare_jid=bare_jid, first=first, last=last})
130
131 -- @nickname ...
132 elseif has_preffix and not has_suffix then
133 if body:sub(last + 1, last + 1) == " " then
134 table.insert(mentions, {bare_jid=bare_jid, first=first, last=last})
135 end
136
137 -- nickname: ...
138 elseif not has_preffix and has_suffix then
139 if body:sub(first - 1, first - 1) == " " then
140 table.insert(mentions, {bare_jid=bare_jid, first=first, last=last})
141 end
142 end
143 end
144 end
145 end
146
147 return mentions
148 end
149
150 local function muc_inject_mentions(event)
151 local room, stanza = event.room, event.stanza;
152 -- Inject mentions only if the room is configured for them
153 if not is_room_eligible(room.jid) then return; end
154 -- Only act on messages that do not include references.
155 -- If references are found, it is assumed the client has mentions support
156 if stanza:get_child("reference", reference_xmlns) then return; end
157
158 local mentions = search_mentions(room, stanza)
159 for _, mention in ipairs(mentions) do
160 -- https://xmpp.org/extensions/xep-0372.html#usecase_mention
161 stanza:tag(
162 "reference", {
163 xmlns=reference_xmlns,
164 begin=tostring(mention.first - 1), -- count starts at 0
165 ["end"]=tostring(mention.last - 1),
166 type="mention",
167 uri="xmpp:" .. mention.bare_jid,
168 }
169 ):up()
170 end
171 end
172
173 module:hook("muc-occupant-groupchat", muc_inject_mentions)