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