--- activate under the main muc component local MIN = module:get_option_number("conference_max_minutes", 5) local TIMEOUT = MIN * 60 -- Gratis time granted past the limit so people can say their goodbyes and -- leave on their own instead of being cut off mid-sentence. Clients still see -- their countdown end at TIMEOUT - they spend the grace period in the visible -- over-schedule state, which is the cue to wrap up - the room is simply not -- torn down until it elapses. 0 (the default) destroys the room the moment the -- limit is reached. local GRACE = module:get_option_number("conference_grace_seconds", 0) local DESTROY_AT = TIMEOUT + GRACE local is_healthcheck_room = module:require "util".is_healthcheck_room local st = require "util.stanza" local json = require "cjson.safe" local array = require "util.array"; module:log('info', "loaded") local restricted_rooms = array{}; -- The `type` the time-timer feature in jitsi-meet matches on. Sent as a -- json-message from the room JID so lib-jitsi-meet surfaces it as a -- NON_PARTICIPANT_MESSAGE_RECEIVED (a server-originated, non-participant -- message) rather than a participant endpoint message. local TIME_RESTRICTED_MESSAGE_TYPE = 'time_restricted'; -- Sends the time-timer json-message to a single occupant. `durationSeconds` is -- the full meeting length and `elapsedSeconds` how far in we are, so every -- client - first joiner or last - lands on the same countdown. local function send_time_timer(room, to_jid) local elapsed = os.time() - (room.time_restricted_created or os.time()); local body, error = json.encode({ type = TIME_RESTRICTED_MESSAGE_TYPE; durationSeconds = TIMEOUT; elapsedSeconds = elapsed; }); if not body then module:log('error', 'failed to encode time-timer message room:%s error:%s', room.jid, error); return; end -- from = room.jid so the stanza routes through ChatRoom.onMessage in -- lib-jitsi-meet (which only processes messages whose bare from matches the -- room) and is emitted as JSON_MESSAGE_RECEIVED -> NON_PARTICIPANT_MESSAGE_RECEIVED. local stanza = st.message({ from = room.jid; to = to_jid; }) :tag('json-message', { xmlns = 'http://jitsi.org/jitmeet' }):text(body):up(); module:send(stanza); end module:hook("muc-room-created", function (event) local room = event.room if is_healthcheck_room(room.jid) then return end room.time_restricted_created = os.time(); room.destroy_timer = module:add_timer(DESTROY_AT, function() if is_healthcheck_room(room.jid) then return end restricted_rooms[room.jid] = true; -- %g (not %d) so a fractional conference_max_minutes does not crash the -- timer; for the usual whole-minute configs it renders identically. room:destroy(nil, string.format('This meeting reached its %g-minute time limit and has ended.', MIN)); module:log('info', "the conference terminated %s", room.jid); end) end) -- Every occupant is told the meeting's limit as soon as they join, so the -- client holds the full picture from the start and decides for itself when to -- surface it (`timeTimer.suppressForSeconds` in jitsi-meet delays the visible -- countdown by a configurable number of seconds). `elapsedSeconds` keeps late -- joiners in sync with everyone else. module:hook("muc-occupant-joined", function (event) local room, occupant = event.room, event.occupant; if is_healthcheck_room(room.jid) then return end -- Only for rooms this module saw created: without a creation stamp we -- cannot say how far in the meeting is, and reporting elapsed = 0 for a -- room that has been running for a while would be worse than saying -- nothing. if room.time_restricted_created then send_time_timer(room, occupant.jid); end end); module:hook("muc-room-pre-create", function(event) local origin, stanza, room = event.origin, event.stanza, event.room; if is_healthcheck_room(room.jid) then return end if restricted_rooms[room.jid] then module:log('info', "the conference id blocked %s", room.jid); origin.send(st.error_reply(stanza, "cancel", "resource-constraint", nil, module.host)); return true; end end, 200); module:hook('muc-room-destroyed',function(event) local room = event.room; if room.destroy_timer then room.destroy_timer:stop(); room.destroy_timer = nil; end end, 1); -- prosody handles it at 0