Você não pode selecionar mais de 25 tópicos Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.

mod_muc_poltergeist.lua 12KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375
  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. -- Removes poltergeist values from table
  59. -- @param room the room instance
  60. -- @param nick the user nick
  61. function remove_username(room, nick)
  62. local room_name = jid.node(room.jid);
  63. if (poltergeists[room_name]) then
  64. local user_id_to_remove;
  65. for name,username in pairs(poltergeists[room_name]) do
  66. if (string.sub(username, 0, 8) == nick) then
  67. user_id_to_remove = name;
  68. end
  69. end
  70. if (user_id_to_remove) then
  71. poltergeists[room_name][user_id_to_remove] = nil;
  72. end
  73. end
  74. end
  75. -- if we found that a session for a user with id has a poltergiest already
  76. -- created, retrieve its jid and return it to the authentication
  77. -- so we can reuse it and we that real user will replace the poltergiest
  78. prosody.events.add_handler("pre-jitsi-authentication", function(session)
  79. if (session.jitsi_meet_context_user) then
  80. local room = get_room(
  81. session.jitsi_bosh_query_room,
  82. session.jitsi_meet_context_group);
  83. if (not room) then
  84. return nil;
  85. end
  86. local username
  87. = get_username(room, session.jitsi_meet_context_user["id"]);
  88. if (not username) then
  89. return nil;
  90. end
  91. log("debug", "Found predefined username %s", username);
  92. -- let's find the room and if the poltergeist occupant is there
  93. -- lets remove him before the real participant joins
  94. -- when we see the unavailable presence to go out the server
  95. -- we will mark it with ignore tag
  96. local nick = string.sub(username, 0, 8);
  97. if (have_poltergeist_occupant(room, nick)) then
  98. remove_poltergeist_occupant(room, nick, true);
  99. end
  100. return username;
  101. end
  102. return nil;
  103. end);
  104. -- Creates poltergeist occupant
  105. -- @param room the room instance where we create the occupant
  106. -- @param nick the nick to use for the new occupant
  107. -- @param name the display name fot the occupant (optional)
  108. -- @param avatar the avatar to use for the new occupant (optional)
  109. function create_poltergeist_occupant(room, nick, name, avatar)
  110. log("debug", "create_poltergeist_occupant %s:", nick);
  111. -- Join poltergeist occupant to room, with the invited JID as their nick
  112. local join_presence = st.presence({
  113. to = room.jid.."/"..nick,
  114. from = poltergeist_component.."/"..nick
  115. }):tag("x", { xmlns = "http://jabber.org/protocol/muc" }):up();
  116. if (name) then
  117. join_presence:tag(
  118. "nick",
  119. { xmlns = "http://jabber.org/protocol/nick" }):text(name):up();
  120. end
  121. if (avatar) then
  122. join_presence:tag("avatar-url"):text(avatar):up();
  123. end
  124. room:handle_first_presence(
  125. prosody.hosts[poltergeist_component], join_presence);
  126. timer.add_task(poltergeist_timeout,
  127. function ()
  128. if (have_poltergeist_occupant(room, nick)) then
  129. remove_poltergeist_occupant(room, nick, false);
  130. end
  131. end);
  132. end
  133. -- Removes poltergeist occupant
  134. -- @param room the room instance where to remove the occupant
  135. -- @param nick the nick of the occupant to remove
  136. -- @param ignore to mark the poltergeist unavailble presence to be ignored
  137. function remove_poltergeist_occupant(room, nick, ignore)
  138. log("debug", "remove_poltergeist_occupant %s", nick);
  139. local leave_presence = st.presence({
  140. to = room.jid.."/"..nick,
  141. from = poltergeist_component.."/"..nick,
  142. type = "unavailable" });
  143. if (ignore) then
  144. poltergeists_pr_ignore[room.jid.."/"..nick] = true;
  145. end
  146. room:handle_normal_presence(
  147. prosody.hosts[poltergeist_component], leave_presence);
  148. remove_username(room, nick);
  149. end
  150. -- Checks for existance of a poltergeist occupant
  151. -- @param room the room instance where to check for occupant
  152. -- @param nick the nick of the occupant
  153. -- @return true if occupant is found, false otherwise
  154. function have_poltergeist_occupant(room, nick)
  155. -- Find out if we have a poltergeist occupant in the room for this JID
  156. return not not room:get_occupant_jid(poltergeist_component.."/"..nick);
  157. end
  158. -- Returns the last presence of occupant
  159. -- @param room the room instance where to check for occupant
  160. -- @param nick the nick of the occupant
  161. -- @return presence of the occupant
  162. function get_presence(room, nick)
  163. local occupant_jid
  164. = room:get_occupant_jid(poltergeist_component.."/"..nick);
  165. if (occupant_jid) then
  166. return room:get_occupant_by_nick(occupant_jid):get_presence();
  167. end
  168. return nil;
  169. end
  170. -- Event handlers
  171. --- Note: mod_muc and some of its sub-modules add event handlers between 0 and -100,
  172. --- e.g. to check for banned users, etc.. Hence adding these handlers at priority -100.
  173. module:hook("muc-decline", function (event)
  174. remove_poltergeist_occupant(event.room, bare(event.stanza.attr.from), false);
  175. end, -100);
  176. -- before sending the presence for a poltergeist leaving add ignore tag
  177. -- as poltergeist is leaving just before the real user joins and in the client
  178. -- we ignore this presence to avoid leaving/joining experience and the real
  179. -- user will reuse all currently created UI components for the same nick
  180. module:hook("muc-broadcast-presence", function (event)
  181. if (bare(event.occupant.jid) == poltergeist_component) then
  182. if(event.stanza.attr.type == "unavailable"
  183. and poltergeists_pr_ignore[event.occupant.nick]) then
  184. event.stanza:tag(
  185. "ignore", { xmlns = "http://jitsi.org/jitmeet/" }):up();
  186. poltergeists_pr_ignore[event.occupant.nick] = nil;
  187. end
  188. end
  189. end, -100);
  190. -- cleanup room table after room is destroyed
  191. module:hook("muc-room-destroyed",function(event)
  192. local room_name = jid.node(event.room.jid);
  193. if (poltergeists[room_name]) then
  194. poltergeists[room_name] = nil;
  195. end
  196. end);
  197. --- Handles request for creating/managing poltergeists
  198. -- @param event the http event, holds the request query
  199. -- @return GET response, containing a json with response details
  200. function handle_create_poltergeist (event)
  201. if (not event.request.url.query) then
  202. return 400;
  203. end
  204. local params = parse(event.request.url.query);
  205. local user_id = params["user"];
  206. local room_name = params["room"];
  207. local group = params["group"];
  208. local name = params["name"];
  209. local avatar = params["avatar"];
  210. local room = get_room(room_name, group);
  211. if (not room) then
  212. log("error", "no room found %s", room_name);
  213. return 404;
  214. end
  215. local username = generate_uuid();
  216. store_username(room, user_id, username)
  217. create_poltergeist_occupant(room, string.sub(username,0,8), name, avatar);
  218. return 200;
  219. end
  220. --- Handles request for updating poltergeists status
  221. -- @param event the http event, holds the request query
  222. -- @return GET response, containing a json with response details
  223. function handle_update_poltergeist (event)
  224. if (not event.request.url.query) then
  225. return 400;
  226. end
  227. local params = parse(event.request.url.query);
  228. local user_id = params["user"];
  229. local room_name = params["room"];
  230. local group = params["group"];
  231. local status = params["status"];
  232. local room = get_room(room_name, group);
  233. if (not room) then
  234. log("error", "no room found %s", room_name);
  235. return 404;
  236. end
  237. local username = get_username(room, user_id);
  238. if (not username) then
  239. return 404;
  240. end
  241. local nick = string.sub(username, 0, 8);
  242. if (have_poltergeist_occupant(room, nick)) then
  243. local update_presence = get_presence(room, nick);
  244. if (not update_presence) then
  245. -- no presence found for occupant, create one
  246. update_presence = st.presence({
  247. to = room.jid.."/"..nick,
  248. from = poltergeist_component.."/"..nick
  249. });
  250. else
  251. -- update occupant presence with appropriate to and from
  252. -- so we can send it again
  253. update_presence = st.clone(update_presence);
  254. update_presence.attr.to = room.jid.."/"..nick;
  255. update_presence.attr.from = poltergeist_component.."/"..nick;
  256. end
  257. local once = false;
  258. -- the status tag we will attach
  259. local statusTag = st.stanza("status"):text(status);
  260. -- if there is already a status tag replace it
  261. update_presence:maptags(function (tag)
  262. if tag.name == statusTag.name then
  263. if not once then
  264. once = true;
  265. return statusTag;
  266. else
  267. return nil;
  268. end
  269. end
  270. return tag;
  271. end);
  272. if (not once) then
  273. -- no status tag was repleced, attach it
  274. update_presence:add_child(statusTag);
  275. end
  276. room:handle_normal_presence(
  277. prosody.hosts[poltergeist_component], update_presence);
  278. return 200;
  279. else
  280. return 404;
  281. end
  282. end
  283. --- Handles remove poltergeists
  284. -- @param event the http event, holds the request query
  285. -- @return GET response, containing a json with response details
  286. function handle_remove_poltergeist (event)
  287. if (not event.request.url.query) then
  288. return 400;
  289. end
  290. local params = parse(event.request.url.query);
  291. local user_id = params["user"];
  292. local room_name = params["room"];
  293. local group = params["group"];
  294. local room = get_room(room_name, group);
  295. if (not room) then
  296. log("error", "no room found %s", room_name);
  297. return 404;
  298. end
  299. local username = get_username(room, user_id);
  300. if (not username) then
  301. return 404;
  302. end
  303. local nick = string.sub(username, 0, 8);
  304. if (have_poltergeist_occupant(room, nick)) then
  305. remove_poltergeist_occupant(room, nick, false);
  306. return 200;
  307. else
  308. return 404;
  309. end
  310. end
  311. log("info", "Loading poltergeist service");
  312. module:depends("http");
  313. module:provides("http", {
  314. default_path = "/";
  315. name = "poltergeist";
  316. route = {
  317. ["GET /poltergeist/create"] = handle_create_poltergeist;
  318. ["GET /poltergeist/update"] = handle_update_poltergeist;
  319. ["GET /poltergeist/remove"] = handle_remove_poltergeist;
  320. };
  321. });