From 6ae5adcb3d83485fa55067b59fb474aeb2ef0679 Mon Sep 17 00:00:00 2001 From: Jacob MacElroy Date: Fri, 15 Jun 2018 19:28:05 +0000 Subject: [PATCH] Creating a poltergiest library and using in for mod_muc_poltergeist. --- resources/prosody-plugins/mod_muc_call.lua | 3 +- .../prosody-plugins/mod_muc_poltergeist.lua | 359 +++-------------- resources/prosody-plugins/poltergeist.lib.lua | 372 ++++++++++++++++++ 3 files changed, 419 insertions(+), 315 deletions(-) create mode 100644 resources/prosody-plugins/poltergeist.lib.lua diff --git a/resources/prosody-plugins/mod_muc_call.lua b/resources/prosody-plugins/mod_muc_call.lua index 66c5e9648..e6f714aea 100644 --- a/resources/prosody-plugins/mod_muc_call.lua +++ b/resources/prosody-plugins/mod_muc_call.lua @@ -39,8 +39,7 @@ local function url_from_room_jid(room_jid) if not(target_node or target_subdomain) then return "https://"..muc_domain_base.."/"..node else - return - "https://"..muc_domain_base.."/"..target_subdomain.."/"..target_node + return "https://"..muc_domain_base.."/"..target_subdomain.."/"..target_node end end diff --git a/resources/prosody-plugins/mod_muc_poltergeist.lua b/resources/prosody-plugins/mod_muc_poltergeist.lua index 8a7252d5c..be1fc9e19 100644 --- a/resources/prosody-plugins/mod_muc_poltergeist.lua +++ b/resources/prosody-plugins/mod_muc_poltergeist.lua @@ -1,22 +1,15 @@ local bare = require "util.jid".bare; -local generate_uuid = require "util.uuid".generate; +local get_room_from_jid = module:require "util".get_room_from_jid; local jid = require "util.jid"; local neturl = require "net.url"; local parse = neturl.parseQuery; -local st = require "util.stanza"; -local get_room_from_jid = module:require "util".get_room_from_jid; +local poltergeist = module:require "poltergeist"; local wrap_async_run = module:require "util".wrap_async_run; -local update_presence_identity = module:require "util".update_presence_identity; -local timer = require "util.timer"; -local MUC_NS = "http://jabber.org/protocol/muc"; -local expired_status = "expired"; -- Options local poltergeist_component = module:get_option_string("poltergeist_component", module.host); --- defaults to 30 seconds -local poltergeist_timeout - = module:get_option_string("poltergeist_leave_timeout", 30); + -- this basically strips the domain from the conference.domain address local parentHostName = string.gmatch(tostring(module.host), "%w+.(%w.+)")(); if parentHostName == nil then @@ -37,12 +30,6 @@ local token_util = module:require "token/util".new(parentCtx); local disableTokenVerification = module:get_option_boolean("disable_polergeist_token_verification", false); --- table to store all poltergeists we create -local poltergeists = {}; --- table to mark that outgoing unavailable presences --- should be marked with ignore -local poltergeists_pr_ignore = {}; - -- poltergaist management functions -- Returns the room if available, work and in multidomain mode @@ -60,54 +47,6 @@ function get_room(room_name, group) return get_room_from_jid(room_address); end --- Stores the username in the table where we store poltergeist usernames --- based on their room names --- @param room the room instance --- @param user_id the user id --- @param username the username to store -function store_username(room, user_id, username) - local room_name = jid.node(room.jid); - - -- we store in poltergeist user ids for room names - if (not poltergeists[room_name]) then - poltergeists[room_name] = {}; - end - poltergeists[room_name][user_id] = username; - log("debug", "stored in session: %s", username); -end - --- Retrieve the username for a user --- @param room the room instance --- @param user_id the user id --- @return returns the stored username for user or nil -function get_username(room, user_id) - local room_name = jid.node(room.jid); - - if (not poltergeists[room_name]) then - return nil; - end - - return poltergeists[room_name][user_id]; -end - --- Removes poltergeist values from table --- @param room the room instance --- @param nick the user nick -function remove_username(room, nick) - local room_name = jid.node(room.jid); - if (poltergeists[room_name]) then - local user_id_to_remove; - for name,username in pairs(poltergeists[room_name]) do - if (string.sub(username, 0, 8) == nick) then - user_id_to_remove = name; - end - end - if (user_id_to_remove) then - poltergeists[room_name][user_id_to_remove] = nil; - end - end -end - --- Verifies room name, domain name with the values in the token -- @param token the token we received -- @param room_name the room name @@ -151,6 +90,8 @@ function verify_token(token, room_name, group, session) return true; end +-- Event handlers + -- if we found that a session for a user with id has a poltergiest already -- created, retrieve its jid and return it to the authentication -- so we can reuse it and we that real user will replace the poltergiest @@ -165,8 +106,10 @@ prosody.events.add_handler("pre-jitsi-authentication", function(session) return nil; end - local username - = get_username(room, session.jitsi_meet_context_user["id"]); + local username = poltergeist.get_username( + room, + session.jitsi_meet_context_user["id"] + ); if (not username) then return nil; @@ -178,12 +121,12 @@ prosody.events.add_handler("pre-jitsi-authentication", function(session) -- lets remove him before the real participant joins -- when we see the unavailable presence to go out the server -- we will mark it with ignore tag - local nick = string.sub(username, 0, 8); - if (have_poltergeist_occupant(room, nick)) then + local nick = poltergeist.create_nick(username); + if (poltergeist.occupies(room, nick)) then + module:log("info", "swapping poltergeist for user: %s/%s", room, nick) -- notify that user connected using the poltergeist - update_poltergeist_occupant_status( - room, nick, "connected"); - remove_poltergeist_occupant(room, nick, true); + poltergeist.update(room, nick, "connected"); + poltergeist.remove(room, nick, true); end return username; @@ -192,134 +135,10 @@ prosody.events.add_handler("pre-jitsi-authentication", function(session) return nil; end); --- Removes poltergeist occupant --- @param room the room instance where to remove the occupant --- @param nick the nick of the occupant to remove --- @param ignore to mark the poltergeist unavailble presence to be ignored -function remove_poltergeist_occupant(room, nick, ignore) - log("debug", "remove_poltergeist_occupant %s", nick); - - local current_presence = get_presence(room, nick); - if (not current_presence) then - module:log("info", "attempted to remove a poltergeist with no presence") - return; - end - - local leave_presence = st.clone(current_presence) - leave_presence.attr.to = room.jid.."/"..nick; - leave_presence.attr.from = poltergeist_component.."/"..nick; - leave_presence.attr.type = "unavailable"; - - if (ignore) then - poltergeists_pr_ignore[room.jid.."/"..nick] = true; - end - - room:handle_normal_presence( - prosody.hosts[poltergeist_component], leave_presence); - remove_username(room, nick); -end - --- Updates poltergeist occupant status --- @param room the room instance where to remove the occupant --- @param nick the nick of the occupant to remove --- @param status the status to update --- @param call_details is a table of call flow details -function update_poltergeist_occupant_status(room, nick, status, call_details) - local update_presence = get_presence(room, nick); - - if (not update_presence) then - -- TODO: determine if we should provide an error and how that would be - -- handled for bosh and http api. - module:log("info", "update issued for a non-existing poltergeist") - return; - end - - -- update occupant presence with appropriate to and from - -- so we can send it again - update_presence = st.clone(update_presence); - update_presence.attr.to = room.jid.."/"..nick; - update_presence.attr.from = poltergeist_component.."/"..nick; - - update_presence = update_presence_tags(update_presence, status, call_details) - - room:handle_normal_presence( - prosody.hosts[poltergeist_component], update_presence); -end - --- Updates the status tags and call flow tags of an existing poltergeist's --- presence. --- @param presence_stanza is the actual presence stanza for a poltergeist. --- @param status is the new status to be updated in the stanza. --- @param call_details is a table of call flow signal information. -function update_presence_tags(presence_stanza, status, call_details) - local call_cancel = false; - local call_id = nil; - - -- Extract optional call flow signal information. - if call_details then - call_id = call_details["id"]; - - if call_details["cancel"] then - call_cancel = call_details["cancel"]; - end - end - - presence_stanza:maptags(function (tag) - if tag.name == "status" then - if call_cancel then - -- If call cancel is set then the status should not be changed. - return tag - end - return st.stanza("status"):text(status); - elseif tag.name == "call_id" then - if call_id then - return st.stanza("call_id"):text(call_id); - else - -- If no call id is provided the re-use the existing id. - return tag; - end - elseif tag.name == "call_cancel" then - if call_cancel then - return st.stanza("call_cancel"):text("true"); - else - return st.stanza("call_cancel"):text("false"); - end - end - return tag; - end); - - return presence_stanza -end - --- Checks for existance of a poltergeist occupant --- @param room the room instance where to check for occupant --- @param nick the nick of the occupant --- @return true if occupant is found, false otherwise -function have_poltergeist_occupant(room, nick) - -- Find out if we have a poltergeist occupant in the room for this JID - return not not room:get_occupant_jid(poltergeist_component.."/"..nick); -end - --- Returns the last presence of occupant --- @param room the room instance where to check for occupant --- @param nick the nick of the occupant --- @return presence of the occupant -function get_presence(room, nick) - local occupant_jid - = room:get_occupant_jid(poltergeist_component.."/"..nick); - if (occupant_jid) then - return room:get_occupant_by_nick(occupant_jid):get_presence(); - end - - return nil; -end - --- Event handlers - --- Note: mod_muc and some of its sub-modules add event handlers between 0 and -100, --- e.g. to check for banned users, etc.. Hence adding these handlers at priority -100. module:hook("muc-decline", function (event) - remove_poltergeist_occupant(event.room, bare(event.stanza.attr.from), false); + poltergeist.remove(event.room, bare(event.stanza.attr.from), false); end, -100); -- before sending the presence for a poltergeist leaving add ignore tag -- as poltergeist is leaving just before the real user joins and in the client @@ -328,21 +147,21 @@ end, -100); module:hook("muc-broadcast-presence", function (event) if (bare(event.occupant.jid) == poltergeist_component) then if(event.stanza.attr.type == "unavailable" - and poltergeists_pr_ignore[event.occupant.nick]) then + and poltergeist.should_ignore(event.occupant.nick)) then event.stanza:tag( "ignore", { xmlns = "http://jitsi.org/jitmeet/" }):up(); - poltergeists_pr_ignore[event.occupant.nick] = nil; + poltergeist.reset_ignored(event.occupant.nick); end end end, -100); -- cleanup room table after room is destroyed -module:hook("muc-room-destroyed",function(event) - local room_name = jid.node(event.room.jid); - if (poltergeists[room_name]) then - poltergeists[room_name] = nil; - end -end); +module:hook( + "muc-room-destroyed", + function(event) + poltergeist.remove_room(event.room); + end +); --- Handles request for creating/managing poltergeists -- @param event the http event, holds the request query @@ -375,115 +194,29 @@ function handle_create_poltergeist (event) -- If the poltergiest is already in the conference then it will -- be in our username store and another can't be added. - local username = get_username(room, user_id); - if (username ~= nil - and have_poltergeist_occupant(room, string.sub(username, 0, 8))) then - log("warn", "poltergeist for username:%s already in the room:%s", - username, room_name); + local username = poltergeist.get_username(room, user_id); + if (username ~=nil and + poltergeist.occupies(room, poltergeist.create_nick(username))) then + log("warn", + "poltergeist for username:%s already in the room:%s", + username, + room_name + ); return 202; end - username = generate_uuid(); + local context = { user = { - id = user_id; + id = user_id; }; group = group; creator_user = session.jitsi_meet_context_user; creator_group = session.jitsi_meet_context_group; }; - - local nick = string.sub(username, 0, 8) - local presence_stanza = original_presence( - poltergeist_component, - room, - nick, - name, - avatar, - username, - context, - status - ) - store_username(room, user_id, username); - - room:handle_first_presence( - prosody.hosts[poltergeist_component], - presence_stanza - ); - - -- the timeout before removing so participants can see the status update - local removeTimeout = 5; - local timeout = poltergeist_timeout - removeTimeout; - - timer.add_task(timeout, - function () - update_poltergeist_occupant_status( - room, nick, expired_status); - -- and remove it after some time so participant can see - -- the update - timer.add_task(removeTimeout, - function () - if (have_poltergeist_occupant(room, nick)) then - remove_poltergeist_occupant(room, nick, false); - end - end); - end); - + poltergeist.add_to_muc(room, user_id, name, avatar, context, status) return 200; end --- Generate the original presence for a poltergeist when it is added to a room. --- @param component is the configured component name for poltergeist. --- @param room is the room the poltergeist is being added to. --- @param nick is the nick the poltergeist will use for xmpp. --- @param avatar is the url of the display avatar for the poltergeist. --- @param username is the poltergeist unique username. --- @param context is the context information from the valid auth token. --- @param status is the status string for the presence. --- @return a presence stanza -function original_presence( - component, room, nick, name, avatar, username, context, status) - local p = st.presence({ - to = room.jid.."/"..nick, - from = component.."/"..nick, - }):tag("x", { xmlns = MUC_NS }):up(); - - p:tag("bot", { type = "poltergeist" }):up(); - p:tag("call_cancel"):text(nil):up(); - p:tag("call_id"):text(username):up(); - - if status then - p:tag("status"):text(status):up(); - else - p:tag("status"):text(nil):up(); - end - - if (name) then - p:tag( - "nick", - { xmlns = "http://jabber.org/protocol/nick" }):text(name):up(); - end - - if (avatar) then - p:tag("avatar-url"):text(avatar):up(); - end - - -- If the room has a password set, let the poltergeist enter using it - local room_password = room:get_password(); - if room_password then - local join = p:get_child("x", MUC_NS); - join:tag("password", { xmlns = MUC_NS }):text(room_password); - end - - update_presence_identity( - p, - context.user, - context.group, - context.creator_user, - context.creator_group - ); - return p -end - --- Handles request for updating poltergeists status -- @param event the http event, holds the request query -- @return GET response, containing a json with response details @@ -514,22 +247,22 @@ function handle_update_poltergeist (event) return 404; end - local username = get_username(room, user_id); + local username = poltergeist.get_username(room, user_id); if (not username) then return 404; end - local call_details = { - ["cancel"] = call_cancel; - ["id"] = call_id; - }; + local call_details = { + ["cancel"] = call_cancel; + ["id"] = call_id; + }; - local nick = string.sub(username, 0, 8); - if (not have_poltergeist_occupant(room, nick)) then + local nick = poltergeist.create_nick(username); + if (not poltergeist.occupies(room, nick)) then return 404; end - update_poltergeist_occupant_status(room, nick, status, call_details); + poltergeist.update(room, nick, status, call_details); return 200; end @@ -556,17 +289,17 @@ function handle_remove_poltergeist (event) return 404; end - local username = get_username(room, user_id); + local username = poltergeist.get_username(room, user_id); if (not username) then return 404; end - local nick = string.sub(username, 0, 8); - if (not have_poltergeist_occupant(room, nick)) then + local nick = poltergeist.create_nick(username); + if (not poltergeist.occupies(room, nick)) then return 404; end - remove_poltergeist_occupant(room, nick, false); + poltergeist.remove(room, nick, false); return 200; end diff --git a/resources/prosody-plugins/poltergeist.lib.lua b/resources/prosody-plugins/poltergeist.lib.lua new file mode 100644 index 000000000..c1564aea6 --- /dev/null +++ b/resources/prosody-plugins/poltergeist.lib.lua @@ -0,0 +1,372 @@ +local inspect = require("inspect") +local jid = require("util.jid") +local stanza = require("util.stanza") +local timer = require("util.timer") +local update_presence_identity = module:require("util").update_presence_identity +local uuid = require("util.uuid") + +local component = module:get_option_string( + "poltergeist_component", + module.host +) + +local expiration_timeout = module:get_option_string( + "poltergeist_leave_timeout", + 30 -- defaults to 30 seconds +) + +local MUC_NS = "http://jabber.org/protocol/muc" + +-------------------------------------------------------------------------------- +-- Utility functions for commonly used poltergeist codes. +-------------------------------------------------------------------------------- + +-- Creates a nick for a poltergeist. +-- @param username is the unique username of the poltergeist +-- @return a nick to use for xmpp +local function create_nick(username) + return string.sub(username, 0,8) +end + +-- Returns the last presence of the occupant. +-- @param room the room instance where to check for occupant +-- @param nick the nick of the occupant +-- @return presence stanza of the occupant +function get_presence(room, nick) + local occupant_jid = room:get_occupant_jid(component.."/"..nick) + if occupant_jid then + return room:get_occupant_by_nick(occupant_jid):get_presence(); + end + return nil; +end + +-- Checks for existance of a poltergeist occupant in a room. +-- @param room the room instance where to check for the occupant +-- @param nick the nick of the occupant +-- @return true if occupant is found, false otherwise +function occupies(room, nick) + -- Find out if we have a poltergeist occupant in the room for this JID + return not not room:get_occupant_jid(component.."/"..nick); +end + + +-------------------------------------------------------------------------------- +-- Username storage for poltergeist. +-- +-- Every poltergeist will have a username stored in a table underneath +-- the room name that they are currently active in. The username can +-- be retrieved given a room and a user_id. The username is removed from +-- a room by providing the room and the nick. +-- +-- A table with a single entry looks like: +-- { +-- ["[hug]hostilewerewolvesthinkslightly"] = { +-- ["655363:52148a3e-b5fb-4cfc-8fbd-f55e793cf657"] = "ed7757d6-d88d-4e6a-8e24-aca2adc31348", +-- ed7757d6 = "655363:52148a3e-b5fb-4cfc-8fbd-f55e793cf657" +-- } +-- } +-------------------------------------------------------------------------------- +-- username is the table where all poltergeist are stored +local usernames = {} + +-- Adds a poltergeist to the store. +-- @param room is the room the poltergeist is being added to +-- @param user_id is the user_id of the user the poltergeist represents +-- @param username is the unique id of the poltergeist itself +local function store_username(room, user_id, username) + local room_name = jid.node(room.jid) + + if not usernames[room_name] then + usernames[room_name] = {} + end + + usernames[room_name][user_id] = username + usernames[room_name][create_nick(username)] = user_id +end + +-- Retrieves a poltergeist username from the store if one exists. +-- @param room is the room to check for the poltergeist in the store +-- @param user_id is the user id of the user the poltergeist represents +local function get_username(room, user_id) + local room_name = jid.node(room.jid) + + if not usernames[room_name] then + return nil + end + + return usernames[room_name][user_id] +end + +local function get_username_from_nick(room_name, nick) + if not usernames[room_name] then + return nil + end + + local user_id = usernames[room_name][nick] + return usernames[room_name][user_id] +end + +-- Removes the username from the store. +-- @param room is the room the poltergeist is being removed from +-- @param nick is the nick of the muc occupant +local function remove_username(room, nick) + local room_name = jid.node(room.jid); + if not usernames[room_name] then + return + end + + local user_id = usernames[room_name][nick] + usernames[room_name][user_id] = nil + usernames[room_name][nick] = nil +end + +-- Removes all poltergeists in the store for the provided room. +-- @param room is the room all poltergiest will be removed from +local function remove_room(room) + local room_name = jid.node(room.jid) + if usernames[room_name] then + usernames[room_name] = nil + end +end + +-------------------------------------------------------------------------------- +-- State for toggling the tagging of presence stanzas with ignored tag. +-- +-- A poltergeist with it's full room/nick set to ignore will have a jitsi ignore +-- tag applied to all presence stanza's broadcasted. The following funcitons +-- assisst in managing this state. +-------------------------------------------------------------------------------- +local presence_ignored = {} + +-- Sets the nick to ignored state. +-- @param room_nick full room/nick jid +local function set_ignored(room_nick) + presence_ignored[room_nick] = true +end + +-- Resets the nick out of ignored state. +-- @param room_nick full room/nick jid +local function reset_ignored(room_nick) + presence_ignored[room_nick] = nil +end + +-- Determines whether or not the leave presence should be tagged with ignored. +-- @param room_nick full room/nick jid +local function should_ignore(room_nick) + if presence_ignored[room_nick] == nil then + return false + end + return presence_ignored[room_nick] +end + +-------------------------------------------------------------------------------- +-- Poltergeist control functions for adding, updating and removing poltergeist. +-------------------------------------------------------------------------------- + +-- Updates the status tags and call flow tags of an existing poltergeist +-- presence. +-- @param presence_stanza is the actual presence stanza for a poltergeist. +-- @param status is the new status to be updated in the stanza. +-- @param call_details is a table of call flow signal information. +function update_presence_tags(presence_stanza, status, call_details) + local call_cancel = false + local call_id = nil + + -- Extract optional call flow signal information. + if call_details then + call_id = call_details["id"] + + if call_details["cancel"] then + call_cancel = call_details["cancel"] + end + end + + presence_stanza:maptags(function (tag) + if tag.name == "status" then + if call_cancel then + -- If call cancel is set then the status should not be changed. + return tag + end + return stanza.stanza("status"):text(status) + elseif tag.name == "call_id" then + if call_id then + return stanza.stanza("call_id"):text(call_id) + else + -- If no call id is provided the re-use the existing id. + return tag + end + elseif tag.name == "call_cancel" then + if call_cancel then + return stanza.stanza("call_cancel"):text("true") + else + return stanza.stanza("call_cancel"):text("false") + end + end + return tag + end) + + return presence_stanza +end + +-- Updates the presence status of a poltergeist. +-- @param room is the room the poltergeist has occupied +-- @param nick is the xmpp nick of the poltergeist occupant +-- @param status is the status string to set in the presence +-- @param call_details is a table of call flow control details +local function update(room, nick, status, call_details) + local original_presence = get_presence(room, nick) + + if not original_presence then + module:log("info", "update issued for a non-existing poltergeist") + return + end + + -- update occupant presence with appropriate to and from + -- so we can send it again + update_presence = stanza.clone(original_presence) + update_presence.attr.to = room.jid.."/"..nick + update_presence.attr.from = component.."/"..nick + + update_presence = update_presence_tags(update_presence, status, call_details) + + module:log("info", "updating poltergeist: %s/%s - %s", room, nick, status) + room:handle_normal_presence( + prosody.hosts[component], + update_presence + ) +end + +-- Removes the poltergeist from the room. +-- @param room is the room the poltergeist has occupied +-- @param nick is the xmpp nick of the poltergeist occupant +-- @param ignore toggles if the leave subsequent leave presence should be tagged +local function remove(room, nick, ignore) + local original_presence = get_presence(room, nick); + if not original_presence then + module:log("info", "attempted to remove a poltergeist with no presence") + return + end + + local leave_presence = stanza.clone(original_presence) + leave_presence.attr.to = room.jid.."/"..nick + leave_presence.attr.from = component.."/"..nick + leave_presence.attr.type = "unavailable" + + if (ignore) then + set_ignored(room, nick) + end + + remove_username(room, nick) + module:log("info", "removing poltergeist: %s/%s", room, nick) + room:handle_normal_presence( + prosody.hosts[component], + leave_presence + ) +end + +-- Adds a poltergeist to a muc/room. +-- @param room is the room the poltergeist will occupy +-- @param is the id of the user the poltergeist represents +-- @param display_name is the display name to use for the poltergeist +-- @param avatar is the avatar link used for the poltergeist display +-- @param context is the session context of the user making the request +-- @param status is the presence status string to use +local function add_to_muc(room, user_id, display_name, avatar, context, status) + local username = uuid.generate() + local presence_stanza = original_presence( + room, + username, + display_name, + avatar, + context, + status + ) + store_username(room, user_id, username) + module:log("info", "adding poltergeist: %s/%s", room, create_nick(username)) + room:handle_first_presence( + prosody.hosts[component], + presence_stanza + ) + local remove_delay = 5 + local expiration = expiration_timeout - remove_delay; + local nick = create_nick(username) + timer.add_task( + expiration, + function () + update(room, nick, "expired") + timer.add_task( + remove_delay, + function () + if occupies(room, nick) then + remove(room, nick, false) + end + end + ) + end + ) +end + +-- Generates an original presence for a new poltergeist +-- @param room is the room the poltergeist will occupy +-- @param username is the unique name for the poltergeist +-- @param display_name is the display name to use for the poltergeist +-- @param avatar is the avatar link used for the poltergeist display +-- @param context is the session context of the user making the request +-- @param status is the presence status string to use +-- @return a presence stanza that can be used to add the poltergeist to the muc +function original_presence(room, username, display_name, avatar, context, status) + local nick = create_nick(username) + local p = stanza.presence({ + to = room.jid.."/"..nick, + from = component.."/"..nick, + }):tag("x", { xmlns = MUC_NS }):up(); + + p:tag("bot", { type = "poltergeist" }):up(); + p:tag("call_cancel"):text(nil):up(); + p:tag("call_id"):text(username):up(); + + if status then + p:tag("status"):text(status):up(); + else + p:tag("status"):text(nil):up(); + end + + if display_name then + p:tag( + "nick", + { xmlns = "http://jabber.org/protocol/nick" }):text(display_name):up(); + end + + if avatar then + p:tag("avatar-url"):text(avatar):up(); + end + + -- If the room has a password set, let the poltergeist enter using it + local room_password = room:get_password(); + if room_password then + local join = p:get_child("x", MUC_NS); + join:tag("password", { xmlns = MUC_NS }):text(room_password); + end + + update_presence_identity( + p, + context.user, + context.group, + context.creator_user, + context.creator_group + ) + return p +end + +return { + get_username = get_username, + get_username_from_nick = get_username_from_nick, + occupies = occupies, + remove_room = remove_room, + reset_ignored = reset_ignored, + should_ignore = should_ignore, + create_nick = create_nick, + add_to_muc = add_to_muc, + update = update, + remove = remove +}