A poltergeist module.

Thanks to Matthew Wild for the initial help of creating these.
Module with REST interface to create poltergeist participants and change their statuses.
When user with same id joins the room, the poltergeist is removed.  We also make sure that that user uses same username when authenticates. This way we are sure that user will join the room with the same nick as the poltergeist.
This commit is contained in:
damencho 2017-07-14 22:18:23 -05:00
parent cc79b073f0
commit 58d06fe7e6
2 changed files with 261 additions and 0 deletions

View File

@ -0,0 +1,240 @@
local bare = require "util.jid".bare;
local generate_uuid = require "util.uuid".generate;
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;
-- Options
local poltergeist_component
= module:get_option_string("poltergeist_component", module.host);
-- table to store all poltergeists we create
local poltergeists = {};
-- poltergaist management functions
-- Returns the room if available, work and in multidomain mode
-- @param room_name the name of the room
-- @param group name of the group (optional)
-- @return returns room if found or nil
function get_room(room_name, group)
local room_address = jid.join(room_name, module:get_host());
-- if there is a group we are in multidomain mode
if group and group ~= "" then
room_address = "["..group.."]"..room_address;
end
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
-- 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
prosody.events.add_handler("pre-jitsi-authentication", function(session)
if (session.jitsi_meet_context_user) then
local room = get_room(
session.jitsi_bosh_query_room,
session.jitsi_meet_context_group);
if (not room) then
return nil;
end
local username
= get_username(room, session.jitsi_meet_context_user["id"]);
if (not username) then
return nil;
end
log("debug", "Found predefined username %s", username);
-- let's find the room and if the poltergeist occupant is there
-- 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
remove_poltergeist_occupant(room, nick);
end
return username;
end
return nil;
end);
-- Creates poltergeist occupant
-- @param room the room instance where we create the occupant
-- @param nick the nick to use for the new occupant
-- @param name the display name fot the occupant (optional)
-- @param avatar the avatar to use for the new occupant (optional)
function create_poltergeist_occupant(room, nick, name, avatar)
log("debug", "create_poltergeist_occupant %s:", nick);
-- Join poltergeist occupant to room, with the invited JID as their nick
local join_presence = st.presence({
to = room.jid.."/"..nick,
from = poltergeist_component.."/"..nick
}):tag("x", { xmlns = "http://jabber.org/protocol/muc" }):up();
if (name) then
join_presence:tag(
"nick",
{ xmlns = "http://jabber.org/protocol/nick" }):text(name):up();
end
if (avatar) then
join_presence:tag("avatar-url"):text(avatar):up();
end
room:handle_first_presence(
prosody.hosts[poltergeist_component], join_presence);
end
-- Removes poltergeist occupant
-- @param room the room instance where to remove the occupant
-- @param nick the nick of the occupant to remove
function remove_poltergeist_occupant(room, nick)
log("debug", "remove_poltergeist_occupant %s", nick);
local leave_presence = st.presence({
to = room.jid.."/"..nick,
from = poltergeist_component.."/"..nick,
type = "unavailable" });
room:handle_normal_presence(
prosody.hosts[poltergeist_component], leave_presence);
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
-- 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));
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
-- we ignore this presence to avoid leaving/joining experience and the real
-- user will reuse all currently created UI components for the same nick
module:hook("muc-broadcast-presence", function (event)
if (bare(event.occupant.jid) == poltergeist_component) then
if(event.stanza.attr.type == "unavailable") then
event.stanza:tag(
"ignore", { xmlns = "http://jitsi.org/jitmeet/" }):up();
end
end
end, -100);
--- Handles request for creating/managing poltergeists
-- @param event the http event, holds the request query
-- @return GET response, containing a json with response details
function handle_create_poltergeist (event)
local params = parse(event.request.url.query);
local user_id = params["user"];
local room_name = params["room"];
local group = params["group"];
local name = params["name"];
local avatar = params["avatar"];
local room = get_room(room_name, group);
if (not room) then
log("error", "no room found %s", room_address);
return 404;
end
local username = generate_uuid();
store_username(room, user_id, username)
create_poltergeist_occupant(room, string.sub(username,0,8), name, avatar);
return 200;
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
function handle_update_poltergeist (event)
local params = parse(event.request.url.query);
local user_id = params["user"];
local room_name = params["room"];
local group = params["group"];
local status = params["status"];
local room = get_room(room_name, group);
if (not room) then
log("error", "no room found %s", room_address);
return 404;
end
local username = get_username(room, user_id);
if (not username) then
return 404;
end
local nick = string.sub(username, 0, 8);
if (have_poltergeist_occupant(room, nick)) then
local update_presence = st.presence({
to = room.jid.."/"..nick,
from = poltergeist_component.."/"..nick
}):tag("status"):text(status):up();
room:handle_normal_presence(
prosody.hosts[poltergeist_component], update_presence);
return 200;
else
return 404;
end
end
log("info", "Loading poltergeist service");
module:depends("http");
module:provides("http", {
default_path = "/";
name = "poltergeist";
route = {
["GET /poltergeist/create"] = handle_create_poltergeist;
["GET /poltergeist/update"] = handle_update_poltergeist;
};
});

View File

@ -0,0 +1,21 @@
local st = require "util.stanza";
-- A component which we use to receive all stanzas for the created poltergeists
-- replays with error if an iq is sent
function no_action()
return true;
end
function error_reply(event)
module:send(st.error_reply(event.stanza, "cancel", "service-unavailable"));
return true;
end
module:hook("presence/host", no_action);
module:hook("message/host", no_action);
module:hook("presence/full", no_action);
module:hook("message/full", no_action);
module:hook("iq/host", error_reply);
module:hook("iq/full", error_reply);
module:hook("iq/bare", error_reply);