Ви не можете вибрати більше 25 тем Теми мають розпочинатися з літери або цифри, можуть містити дефіси (-) і не повинні перевищувати 35 символів.

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397
  1. local inspect = require("inspect")
  2. local jid = require("util.jid")
  3. local stanza = require("util.stanza")
  4. local timer = require("util.timer")
  5. local update_presence_identity = module:require("util").update_presence_identity
  6. local uuid = require("util.uuid")
  7. local component = module:get_option_string(
  8. "poltergeist_component",
  9. module.host
  10. )
  11. local expiration_timeout = module:get_option_string(
  12. "poltergeist_leave_timeout",
  13. 30 -- defaults to 30 seconds
  14. )
  15. local MUC_NS = "http://jabber.org/protocol/muc"
  16. --------------------------------------------------------------------------------
  17. -- Utility functions for commonly used poltergeist codes.
  18. --------------------------------------------------------------------------------
  19. -- Creates a nick for a poltergeist.
  20. -- @param username is the unique username of the poltergeist
  21. -- @return a nick to use for xmpp
  22. local function create_nick(username)
  23. return string.sub(username, 0,8)
  24. end
  25. -- Returns the last presence of the occupant.
  26. -- @param room the room instance where to check for occupant
  27. -- @param nick the nick of the occupant
  28. -- @return presence stanza of the occupant
  29. function get_presence(room, nick)
  30. local occupant_jid = room:get_occupant_jid(component.."/"..nick)
  31. if occupant_jid then
  32. return room:get_occupant_by_nick(occupant_jid):get_presence();
  33. end
  34. return nil;
  35. end
  36. -- Checks for existance of a poltergeist occupant in a room.
  37. -- @param room the room instance where to check for the occupant
  38. -- @param nick the nick of the occupant
  39. -- @return true if occupant is found, false otherwise
  40. function occupies(room, nick)
  41. -- Find out if we have a poltergeist occupant in the room for this JID
  42. return not not room:get_occupant_jid(component.."/"..nick);
  43. end
  44. --------------------------------------------------------------------------------
  45. -- Username storage for poltergeist.
  46. --
  47. -- Every poltergeist will have a username stored in a table underneath
  48. -- the room name that they are currently active in. The username can
  49. -- be retrieved given a room and a user_id. The username is removed from
  50. -- a room by providing the room and the nick.
  51. --
  52. -- A table with a single entry looks like:
  53. -- {
  54. -- ["[hug]hostilewerewolvesthinkslightly"] = {
  55. -- ["655363:52148a3e-b5fb-4cfc-8fbd-f55e793cf657"] = "ed7757d6-d88d-4e6a-8e24-aca2adc31348",
  56. -- ed7757d6 = "655363:52148a3e-b5fb-4cfc-8fbd-f55e793cf657"
  57. -- }
  58. -- }
  59. --------------------------------------------------------------------------------
  60. -- state is the table where poltergeist usernames and call resources are stored
  61. -- for a given xmpp muc.
  62. local state = module:shared("state")
  63. -- Adds a poltergeist to the store.
  64. -- @param room is the room the poltergeist is being added to
  65. -- @param user_id is the user_id of the user the poltergeist represents
  66. -- @param username is the unique id of the poltergeist itself
  67. local function store_username(room, user_id, username)
  68. local room_name = jid.node(room.jid)
  69. if not state[room_name] then
  70. state[room_name] = {}
  71. end
  72. state[room_name][user_id] = username
  73. state[room_name][create_nick(username)] = user_id
  74. end
  75. -- Retrieves a poltergeist username from the store if one exists.
  76. -- @param room is the room to check for the poltergeist in the store
  77. -- @param user_id is the user id of the user the poltergeist represents
  78. local function get_username(room, user_id)
  79. local room_name = jid.node(room.jid)
  80. if not state[room_name] then
  81. return nil
  82. end
  83. return state[room_name][user_id]
  84. end
  85. local function get_username_from_nick(room_name, nick)
  86. if not state[room_name] then
  87. return nil
  88. end
  89. local user_id = state[room_name][nick]
  90. return state[room_name][user_id]
  91. end
  92. -- Removes the username from the store.
  93. -- @param room is the room the poltergeist is being removed from
  94. -- @param nick is the nick of the muc occupant
  95. local function remove_username(room, nick)
  96. local room_name = jid.node(room.jid)
  97. if not state[room_name] then
  98. return
  99. end
  100. local user_id = state[room_name][nick]
  101. state[room_name][user_id] = nil
  102. state[room_name][nick] = nil
  103. end
  104. -- Removes all poltergeists in the store for the provided room.
  105. -- @param room is the room all poltergiest will be removed from
  106. local function remove_room(room)
  107. local room_name = jid.node(room.jid)
  108. if state[room_name] then
  109. state[room_name] = nil
  110. end
  111. end
  112. -- Adds a resource that is associated with a a call in a room. There
  113. -- is only one resource for each type.
  114. -- @param room is the room the call and poltergeist is in.
  115. -- @param call_id is the unique id for the call.
  116. -- @param resource_type is type of resource being added.
  117. -- @param resource_id is the id of the resource being added.
  118. local function add_call_resource(room, call_id, resource_type, resource_id)
  119. local room_name = jid.node(room.jid)
  120. if not state[room_name] then
  121. state[room_name] = {}
  122. end
  123. if not state[room_name][call_id] then
  124. state[room_name][call_id] = {}
  125. end
  126. state[room_name][call_id][resource_type] = resource_id
  127. end
  128. --------------------------------------------------------------------------------
  129. -- State for toggling the tagging of presence stanzas with ignored tag.
  130. --
  131. -- A poltergeist with it's full room/nick set to ignore will have a jitsi ignore
  132. -- tag applied to all presence stanza's broadcasted. The following funcitons
  133. -- assisst in managing this state.
  134. --------------------------------------------------------------------------------
  135. local presence_ignored = {}
  136. -- Sets the nick to ignored state.
  137. -- @param room_nick full room/nick jid
  138. local function set_ignored(room_nick)
  139. presence_ignored[room_nick] = true
  140. end
  141. -- Resets the nick out of ignored state.
  142. -- @param room_nick full room/nick jid
  143. local function reset_ignored(room_nick)
  144. presence_ignored[room_nick] = nil
  145. end
  146. -- Determines whether or not the leave presence should be tagged with ignored.
  147. -- @param room_nick full room/nick jid
  148. local function should_ignore(room_nick)
  149. if presence_ignored[room_nick] == nil then
  150. return false
  151. end
  152. return presence_ignored[room_nick]
  153. end
  154. --------------------------------------------------------------------------------
  155. -- Poltergeist control functions for adding, updating and removing poltergeist.
  156. --------------------------------------------------------------------------------
  157. -- Updates the status tags and call flow tags of an existing poltergeist
  158. -- presence.
  159. -- @param presence_stanza is the actual presence stanza for a poltergeist.
  160. -- @param status is the new status to be updated in the stanza.
  161. -- @param call_details is a table of call flow signal information.
  162. function update_presence_tags(presence_stanza, status, call_details)
  163. local call_cancel = false
  164. local call_id = nil
  165. -- Extract optional call flow signal information.
  166. if call_details then
  167. call_id = call_details["id"]
  168. if call_details["cancel"] then
  169. call_cancel = call_details["cancel"]
  170. end
  171. end
  172. presence_stanza:maptags(function (tag)
  173. if tag.name == "status" then
  174. if call_cancel then
  175. -- If call cancel is set then the status should not be changed.
  176. return tag
  177. end
  178. return stanza.stanza("status"):text(status)
  179. elseif tag.name == "call_id" then
  180. if call_id then
  181. return stanza.stanza("call_id"):text(call_id)
  182. else
  183. -- If no call id is provided the re-use the existing id.
  184. return tag
  185. end
  186. elseif tag.name == "call_cancel" then
  187. if call_cancel then
  188. return stanza.stanza("call_cancel"):text("true")
  189. else
  190. return stanza.stanza("call_cancel"):text("false")
  191. end
  192. end
  193. return tag
  194. end)
  195. return presence_stanza
  196. end
  197. -- Updates the presence status of a poltergeist.
  198. -- @param room is the room the poltergeist has occupied
  199. -- @param nick is the xmpp nick of the poltergeist occupant
  200. -- @param status is the status string to set in the presence
  201. -- @param call_details is a table of call flow control details
  202. local function update(room, nick, status, call_details)
  203. local original_presence = get_presence(room, nick)
  204. if not original_presence then
  205. module:log("info", "update issued for a non-existing poltergeist")
  206. return
  207. end
  208. -- update occupant presence with appropriate to and from
  209. -- so we can send it again
  210. update_presence = stanza.clone(original_presence)
  211. update_presence.attr.to = room.jid.."/"..nick
  212. update_presence.attr.from = component.."/"..nick
  213. update_presence = update_presence_tags(update_presence, status, call_details)
  214. module:log("info", "updating poltergeist: %s/%s - %s", room, nick, status)
  215. room:handle_normal_presence(
  216. prosody.hosts[component],
  217. update_presence
  218. )
  219. end
  220. -- Removes the poltergeist from the room.
  221. -- @param room is the room the poltergeist has occupied
  222. -- @param nick is the xmpp nick of the poltergeist occupant
  223. -- @param ignore toggles if the leave subsequent leave presence should be tagged
  224. local function remove(room, nick, ignore)
  225. local original_presence = get_presence(room, nick);
  226. if not original_presence then
  227. module:log("info", "attempted to remove a poltergeist with no presence")
  228. return
  229. end
  230. local leave_presence = stanza.clone(original_presence)
  231. leave_presence.attr.to = room.jid.."/"..nick
  232. leave_presence.attr.from = component.."/"..nick
  233. leave_presence.attr.type = "unavailable"
  234. if (ignore) then
  235. set_ignored(room.jid.."/"..nick)
  236. end
  237. remove_username(room, nick)
  238. module:log("info", "removing poltergeist: %s/%s", room, nick)
  239. room:handle_normal_presence(
  240. prosody.hosts[component],
  241. leave_presence
  242. )
  243. end
  244. -- Adds a poltergeist to a muc/room.
  245. -- @param room is the room the poltergeist will occupy
  246. -- @param is the id of the user the poltergeist represents
  247. -- @param display_name is the display name to use for the poltergeist
  248. -- @param avatar is the avatar link used for the poltergeist display
  249. -- @param context is the session context of the user making the request
  250. -- @param status is the presence status string to use
  251. -- @param resources is a table of resource types and resource ids to correlate.
  252. local function add_to_muc(room, user_id, display_name, avatar, context, status, resources)
  253. local username = uuid.generate()
  254. local presence_stanza = original_presence(
  255. room,
  256. username,
  257. display_name,
  258. avatar,
  259. context,
  260. status
  261. )
  262. module:log("info", "adding poltergeist: %s/%s", room, create_nick(username))
  263. store_username(room, user_id, username)
  264. for k, v in pairs(resources) do
  265. add_call_resource(room, username, k, v)
  266. end
  267. room:handle_first_presence(
  268. prosody.hosts[component],
  269. presence_stanza
  270. )
  271. local remove_delay = 5
  272. local expiration = expiration_timeout - remove_delay;
  273. local nick = create_nick(username)
  274. timer.add_task(
  275. expiration,
  276. function ()
  277. update(room, nick, "expired")
  278. timer.add_task(
  279. remove_delay,
  280. function ()
  281. if occupies(room, nick) then
  282. remove(room, nick, false)
  283. end
  284. end
  285. )
  286. end
  287. )
  288. end
  289. -- Generates an original presence for a new poltergeist
  290. -- @param room is the room the poltergeist will occupy
  291. -- @param username is the unique name for the poltergeist
  292. -- @param display_name is the display name to use for the poltergeist
  293. -- @param avatar is the avatar link used for the poltergeist display
  294. -- @param context is the session context of the user making the request
  295. -- @param status is the presence status string to use
  296. -- @return a presence stanza that can be used to add the poltergeist to the muc
  297. function original_presence(room, username, display_name, avatar, context, status)
  298. local nick = create_nick(username)
  299. local p = stanza.presence({
  300. to = room.jid.."/"..nick,
  301. from = component.."/"..nick,
  302. }):tag("x", { xmlns = MUC_NS }):up();
  303. p:tag("bot", { type = "poltergeist" }):up();
  304. p:tag("call_cancel"):text(nil):up();
  305. p:tag("call_id"):text(username):up();
  306. if status then
  307. p:tag("status"):text(status):up();
  308. else
  309. p:tag("status"):text(nil):up();
  310. end
  311. if display_name then
  312. p:tag(
  313. "nick",
  314. { xmlns = "http://jabber.org/protocol/nick" }):text(display_name):up();
  315. end
  316. if avatar then
  317. p:tag("avatar-url"):text(avatar):up();
  318. end
  319. -- If the room has a password set, let the poltergeist enter using it
  320. local room_password = room:get_password();
  321. if room_password then
  322. local join = p:get_child("x", MUC_NS);
  323. join:tag("password", { xmlns = MUC_NS }):text(room_password);
  324. end
  325. update_presence_identity(
  326. p,
  327. context.user,
  328. context.group,
  329. context.creator_user,
  330. context.creator_group
  331. )
  332. return p
  333. end
  334. return {
  335. get_username = get_username,
  336. get_username_from_nick = get_username_from_nick,
  337. occupies = occupies,
  338. remove_room = remove_room,
  339. reset_ignored = reset_ignored,
  340. should_ignore = should_ignore,
  341. create_nick = create_nick,
  342. add_to_muc = add_to_muc,
  343. update = update,
  344. remove = remove
  345. }