Mercurial > prosody-modules
view mod_muc_inject_mentions/mod_muc_inject_mentions.lua @ 5787:e79f9dec35c0
mod_c2s_conn_throttle: Reduce log level from error->info
Our general policy is that "error" should never be triggerable by remote
entities, and that it is always about something that requires admin
intervention. This satisfies neither condition.
The "warn" level can be used for unexpected events/behaviour triggered by
remote entities, and this could qualify. However I don't think failed auth
attempts are unexpected enough.
I selected "info" because it is what is also used for other notable session
lifecycle events.
author | Matthew Wild <mwild1@gmail.com> |
---|---|
date | Thu, 07 Dec 2023 15:46:50 +0000 |
parents | 33a41503b9e3 |
children |
line wrap: on
line source
module:depends("muc"); local jid_resource = require "util.jid".resource; local st = require "util.stanza"; 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", "\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_bare_jid = module:get_option("muc_inject_mentions_use_bare_jid", true) local prefix_mandatory = module:get_option("muc_inject_mentions_prefix_mandatory", 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 bare_jid = reserved_nicknames[room.jid][nickname] if bare_jid and use_bare_jid then return bare_jid end if bare_jid and not use_bare_jid then return room.jid .. "/" .. nickname end end local function get_participants(room) if not reserved_nicks then local occupants = room._occupants local key, occupant = next(occupants) return function () while occupant do -- luacheck: ignore local nick = jid_resource(occupant.nick); local bare_jid = occupant.bare_jid key, occupant = next(occupants, key) return bare_jid, nick end end else local generator = room:each_affiliation() local jid, _, affiliation_data = generator(nil, nil) return function () while jid do local bare_jid, nick = jid, (affiliation_data or {})["reserved_nickname"] jid, _, affiliation_data = generator(nil, bare_jid) if nick then return bare_jid, nick end end end end end local function add_mention(mentions, bare_jid, first, last, prefix_indices, has_prefix) if strip_out_prefixes then if has_prefix then table.insert(prefix_indices, first-1) end first = first - #prefix_indices last = last - #prefix_indices end mentions[first] = {bare_jid=bare_jid, first=first, last=last} end local function get_client_mentions(stanza) local has_mentions = false local client_mentions = {} for element in stanza:childtags("reference", reference_xmlns) do if element.attr.type == "mention" then local key = tonumber(element.attr.begin) + 1 -- count starts at 0 client_mentions[key] = {bare_jid=element.attr.uri, first=element.attr.begin, last=element.attr["end"]} has_mentions = true end end return has_mentions, client_mentions end local function is_room_eligible(jid) if not enabled_rooms and not disabled_rooms then return true; end if enabled_rooms then for _, _jid in ipairs(enabled_rooms) do if _jid == jid then return true end end return false end if disabled_rooms then for _, _jid in ipairs(disabled_rooms) do if _jid == jid then return false end end return true end end local function has_nick_prefix(body, first) -- There are no configured prefixes if not prefixes or #prefixes < 1 then return false end -- Prefix must have a space before it, -- be the first character of the body -- or be the first character after a new line if not mention_delimiters:contains(body:sub(first - 2, first - 2)) then return false end local prefix = body:sub(first - 1, first - 1) for _, _prefix in ipairs(prefixes) do if prefix == _prefix then return true end end return false end local function has_nick_suffix(body, last) -- There are no configured suffixes if not suffixes or #suffixes < 1 then return false end -- Suffix must have a space after it, -- be the last character of the body -- or be the last character before a new line if not mention_delimiters:contains(body:sub(last + 2, last + 2)) then return false end local suffix = body:sub(last+1, last+1) for _, _suffix in ipairs(suffixes) do if suffix == _suffix then return true end end return false end local function search_mentions(room, body, client_mentions) load_room_reserved_nicknames(room) local mentions, prefix_indices = {}, {} 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 if not prefix_mandatory then add_mention(mentions, jid, current_word_start, i - 1, prefix_indices, false) end 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 and not prefix_mandatory 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 return mentions, prefix_indices end local function muc_inject_mentions(event) local room, stanza = event.room, event.stanza; local body = stanza:get_child_text("body") 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 -- Only act on messages that do not include mentions -- unless configuration states otherwise. local has_mentions, client_mentions = get_client_mentions(stanza) if has_mentions and not append_mentions then return; end 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( "reference", { xmlns=reference_xmlns, begin=tostring(mention.first - 1), -- count starts at 0 ["end"]=tostring(mention.last), type="mention", uri="xmpp:" .. mention.bare_jid, } ):up() end if strip_out_prefixes then local body_without_prefixes = "" local from = 0 if #prefix_indices > 0 then for _, prefix_index in ipairs(prefix_indices) do body_without_prefixes = body_without_prefixes .. body:sub(from, prefix_index-1) from = prefix_index + 1 end body_without_prefixes = body_without_prefixes .. body:sub(from, #body) -- Replace original body containing prefixes stanza:maptags( function(tag) if tag.name ~= "body" then return tag end return st.stanza("body"):text(body_without_prefixes) end ) end end end module:hook("muc-occupant-groupchat", muc_inject_mentions) module:hook("muc-set-affiliation", update_reserved_nicknames)