view plugins/muc/occupant.lib.lua @ 13955:01f95f3de6fc

usermanager: More consistent method behaviour, errors, and (pre-)events This commit refactors most of the exported usermanager methods substantially to ensure that all of them correctly check for the presence of the host and method they are calling and return consistent errors if either is incorrect or missing. It also unifies the event and logging code, so as a result there are more events being fired than before this commit (it also adds pre- events so that modules can block an action from going ahead). We don't have good test coverage of this API, but some manual testing suggests it seems to be working okay...
author Matthew Wild <mwild1@gmail.com>
date Thu, 25 Sep 2025 16:53:35 +0100
parents 4af7d00a2966
children
line wrap: on
line source

local pairs = pairs;
local setmetatable = setmetatable;
local st = require "prosody.util.stanza";
local util = module:require "muc/util";

local function get_filtered_presence(stanza)
	return util.filter_muc_x(st.clone(stanza));
end

local occupant_mt = {};
occupant_mt.__index = occupant_mt;

local function new_occupant(bare_real_jid, nick)
	return setmetatable({
		bare_jid = bare_real_jid;
		nick = nick; -- in-room jid
		sessions = {}; -- hash from real_jid to presence stanzas. stanzas should not be modified
		role = nil;
		jid = nil; -- Primary session
	}, occupant_mt);
end

-- Deep copy an occupant
local function copy_occupant(occupant)
	local sessions = {};
	for full_jid, session in pairs(occupant.sessions) do
		local presence_stanza = session.presence;
		-- Don't keep unavailable presences, as they'll accumulate; unless they're the primary session
		if presence_stanza.attr.type ~= "unavailable" or full_jid == occupant.jid then
			sessions[full_jid] = session;
		end
	end
	return setmetatable({
		bare_jid = occupant.bare_jid;
		nick = occupant.nick;
		sessions = sessions;
		role = occupant.role;
		jid = occupant.jid;
	}, occupant_mt);
end

-- finds another session to be the primary (there might not be one)
function occupant_mt:choose_new_primary()
	for jid, pr in self:each_session() do
		if pr.attr.type == nil then
			return jid;
		end
	end
	return nil;
end

function occupant_mt:get_session(real_jid)
	return self.sessions[real_jid];
end

function occupant_mt:set_session(real_jid, presence_stanza, replace_primary)
	local pr = get_filtered_presence(presence_stanza);
	pr.attr.from = self.nick;
	pr.attr.to = real_jid;

	self.sessions[real_jid] = { presence = pr };
	if replace_primary then
		self.jid = real_jid;
	elseif self.jid == nil or (pr.attr.type == "unavailable" and self.jid == real_jid) then
		-- Only leave an unavailable presence as primary when there are no other options
		self.jid = self:choose_new_primary() or real_jid;
	end
end

function occupant_mt:remove_session(real_jid)
	-- Delete original session
	self.sessions[real_jid] = nil;
	if self.jid == real_jid then
		self.jid = self:choose_new_primary();
	end
end

-- An iterator over self.sessions that returns (jid, presence, session)
local function _session_iter(s, var)
	local k = next(s, var);
	if k then
		local v = s[k];
		return k, v.presence, v;
	end
end

function occupant_mt:each_session()
	return _session_iter, self.sessions;
end

function occupant_mt:get_presence(real_jid)
	local session = self.sessions[real_jid or self.jid];
	if not session then return; end
	return session.presence;
end

return {
	new = new_occupant;
	copy = copy_occupant;
	mt = occupant_mt;
}