You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

mod_muc_poltergeist.lua 9.8KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302
  1. local bare = require "util.jid".bare;
  2. local generate_uuid = require "util.uuid".generate;
  3. local jid = require "util.jid";
  4. local neturl = require "net.url";
  5. local parse = neturl.parseQuery;
  6. local st = require "util.stanza";
  7. local get_room_from_jid = module:require "util".get_room_from_jid;
  8. local timer = require "util.timer";
  9. -- Options
  10. local poltergeist_component
  11. = module:get_option_string("poltergeist_component", module.host);
  12. -- defaults to 3 min
  13. local poltergeist_timeout
  14. = module:get_option_string("poltergeist_leave_timeout", 180);
  15. -- table to store all poltergeists we create
  16. local poltergeists = {};
  17. -- table to mark that outgoing unavailable presences
  18. -- should be marked with ignore
  19. local poltergeists_pr_ignore = {};
  20. -- poltergaist management functions
  21. -- Returns the room if available, work and in multidomain mode
  22. -- @param room_name the name of the room
  23. -- @param group name of the group (optional)
  24. -- @return returns room if found or nil
  25. function get_room(room_name, group)
  26. local room_address = jid.join(room_name, module:get_host());
  27. -- if there is a group we are in multidomain mode
  28. if group and group ~= "" then
  29. room_address = "["..group.."]"..room_address;
  30. end
  31. return get_room_from_jid(room_address);
  32. end
  33. -- Stores the username in the table where we store poltergeist usernames
  34. -- based on their room names
  35. -- @param room the room instance
  36. -- @param user_id the user id
  37. -- @param username the username to store
  38. function store_username(room, user_id, username)
  39. local room_name = jid.node(room.jid);
  40. -- we store in poltergeist user ids for room names
  41. if (not poltergeists[room_name]) then
  42. poltergeists[room_name] = {};
  43. end
  44. poltergeists[room_name][user_id] = username;
  45. log("debug", "stored in session: %s", username);
  46. end
  47. -- Retrieve the username for a user
  48. -- @param room the room instance
  49. -- @param user_id the user id
  50. -- @return returns the stored username for user or nil
  51. function get_username(room, user_id)
  52. local room_name = jid.node(room.jid);
  53. if (not poltergeists[room_name]) then
  54. return nil;
  55. end
  56. return poltergeists[room_name][user_id];
  57. end
  58. -- if we found that a session for a user with id has a poltergiest already
  59. -- created, retrieve its jid and return it to the authentication
  60. -- so we can reuse it and we that real user will replace the poltergiest
  61. prosody.events.add_handler("pre-jitsi-authentication", function(session)
  62. if (session.jitsi_meet_context_user) then
  63. local room = get_room(
  64. session.jitsi_bosh_query_room,
  65. session.jitsi_meet_context_group);
  66. if (not room) then
  67. return nil;
  68. end
  69. local username
  70. = get_username(room, session.jitsi_meet_context_user["id"]);
  71. if (not username) then
  72. return nil;
  73. end
  74. log("debug", "Found predefined username %s", username);
  75. -- let's find the room and if the poltergeist occupant is there
  76. -- lets remove him before the real participant joins
  77. -- when we see the unavailable presence to go out the server
  78. -- we will mark it with ignore tag
  79. local nick = string.sub(username, 0, 8);
  80. if (have_poltergeist_occupant(room, nick)) then
  81. remove_poltergeist_occupant(room, nick, true);
  82. end
  83. return username;
  84. end
  85. return nil;
  86. end);
  87. -- Creates poltergeist occupant
  88. -- @param room the room instance where we create the occupant
  89. -- @param nick the nick to use for the new occupant
  90. -- @param name the display name fot the occupant (optional)
  91. -- @param avatar the avatar to use for the new occupant (optional)
  92. function create_poltergeist_occupant(room, nick, name, avatar)
  93. log("debug", "create_poltergeist_occupant %s:", nick);
  94. -- Join poltergeist occupant to room, with the invited JID as their nick
  95. local join_presence = st.presence({
  96. to = room.jid.."/"..nick,
  97. from = poltergeist_component.."/"..nick
  98. }):tag("x", { xmlns = "http://jabber.org/protocol/muc" }):up();
  99. if (name) then
  100. join_presence:tag(
  101. "nick",
  102. { xmlns = "http://jabber.org/protocol/nick" }):text(name):up();
  103. end
  104. if (avatar) then
  105. join_presence:tag("avatar-url"):text(avatar):up();
  106. end
  107. room:handle_first_presence(
  108. prosody.hosts[poltergeist_component], join_presence);
  109. timer.add_task(poltergeist_timeout,
  110. function ()
  111. if (have_poltergeist_occupant(room, nick)) then
  112. remove_poltergeist_occupant(room, nick, false);
  113. end
  114. end);
  115. end
  116. -- Removes poltergeist occupant
  117. -- @param room the room instance where to remove the occupant
  118. -- @param nick the nick of the occupant to remove
  119. -- @param ignore to mark the poltergeist unavailble presence to be ignored
  120. function remove_poltergeist_occupant(room, nick, ignore)
  121. log("debug", "remove_poltergeist_occupant %s", nick);
  122. local leave_presence = st.presence({
  123. to = room.jid.."/"..nick,
  124. from = poltergeist_component.."/"..nick,
  125. type = "unavailable" });
  126. if (ignore) then
  127. poltergeists_pr_ignore[room.jid.."/"..nick] = true;
  128. end
  129. room:handle_normal_presence(
  130. prosody.hosts[poltergeist_component], leave_presence);
  131. end
  132. -- Checks for existance of a poltergeist occupant
  133. -- @param room the room instance where to check for occupant
  134. -- @param nick the nick of the occupant
  135. -- @return true if occupant is found, false otherwise
  136. function have_poltergeist_occupant(room, nick)
  137. -- Find out if we have a poltergeist occupant in the room for this JID
  138. return not not room:get_occupant_jid(poltergeist_component.."/"..nick);
  139. end
  140. -- Event handlers
  141. --- Note: mod_muc and some of its sub-modules add event handlers between 0 and -100,
  142. --- e.g. to check for banned users, etc.. Hence adding these handlers at priority -100.
  143. module:hook("muc-decline", function (event)
  144. remove_poltergeist_occupant(event.room, bare(event.stanza.attr.from), false);
  145. end, -100);
  146. -- before sending the presence for a poltergeist leaving add ignore tag
  147. -- as poltergeist is leaving just before the real user joins and in the client
  148. -- we ignore this presence to avoid leaving/joining experience and the real
  149. -- user will reuse all currently created UI components for the same nick
  150. module:hook("muc-broadcast-presence", function (event)
  151. if (bare(event.occupant.jid) == poltergeist_component) then
  152. if(event.stanza.attr.type == "unavailable"
  153. and poltergeists_pr_ignore[event.occupant.nick]) then
  154. event.stanza:tag(
  155. "ignore", { xmlns = "http://jitsi.org/jitmeet/" }):up();
  156. poltergeists_pr_ignore[event.occupant.nick] = nil;
  157. end
  158. end
  159. end, -100);
  160. --- Handles request for creating/managing poltergeists
  161. -- @param event the http event, holds the request query
  162. -- @return GET response, containing a json with response details
  163. function handle_create_poltergeist (event)
  164. if (not event.request.url.query) then
  165. return 400;
  166. end
  167. local params = parse(event.request.url.query);
  168. local user_id = params["user"];
  169. local room_name = params["room"];
  170. local group = params["group"];
  171. local name = params["name"];
  172. local avatar = params["avatar"];
  173. local room = get_room(room_name, group);
  174. if (not room) then
  175. log("error", "no room found %s", room_name);
  176. return 404;
  177. end
  178. local username = generate_uuid();
  179. store_username(room, user_id, username)
  180. create_poltergeist_occupant(room, string.sub(username,0,8), name, avatar);
  181. return 200;
  182. end
  183. --- Handles request for updating poltergeists status
  184. -- @param event the http event, holds the request query
  185. -- @return GET response, containing a json with response details
  186. function handle_update_poltergeist (event)
  187. if (not event.request.url.query) then
  188. return 400;
  189. end
  190. local params = parse(event.request.url.query);
  191. local user_id = params["user"];
  192. local room_name = params["room"];
  193. local group = params["group"];
  194. local status = params["status"];
  195. local room = get_room(room_name, group);
  196. if (not room) then
  197. log("error", "no room found %s", room_name);
  198. return 404;
  199. end
  200. local username = get_username(room, user_id);
  201. if (not username) then
  202. return 404;
  203. end
  204. local nick = string.sub(username, 0, 8);
  205. if (have_poltergeist_occupant(room, nick)) then
  206. local update_presence = st.presence({
  207. to = room.jid.."/"..nick,
  208. from = poltergeist_component.."/"..nick
  209. }):tag("status"):text(status):up();
  210. room:handle_normal_presence(
  211. prosody.hosts[poltergeist_component], update_presence);
  212. return 200;
  213. else
  214. return 404;
  215. end
  216. end
  217. --- Handles remove poltergeists
  218. -- @param event the http event, holds the request query
  219. -- @return GET response, containing a json with response details
  220. function handle_remove_poltergeist (event)
  221. if (not event.request.url.query) then
  222. return 400;
  223. end
  224. local params = parse(event.request.url.query);
  225. local user_id = params["user"];
  226. local room_name = params["room"];
  227. local group = params["group"];
  228. local room = get_room(room_name, group);
  229. if (not room) then
  230. log("error", "no room found %s", room_name);
  231. return 404;
  232. end
  233. local username = get_username(room, user_id);
  234. if (not username) then
  235. return 404;
  236. end
  237. local nick = string.sub(username, 0, 8);
  238. if (have_poltergeist_occupant(room, nick)) then
  239. remove_poltergeist_occupant(room, nick, false);
  240. return 200;
  241. else
  242. return 404;
  243. end
  244. end
  245. log("info", "Loading poltergeist service");
  246. module:depends("http");
  247. module:provides("http", {
  248. default_path = "/";
  249. name = "poltergeist";
  250. route = {
  251. ["GET /poltergeist/create"] = handle_create_poltergeist;
  252. ["GET /poltergeist/update"] = handle_update_poltergeist;
  253. ["GET /poltergeist/remove"] = handle_remove_poltergeist;
  254. };
  255. });