diff --git a/prosody-modules/mod_muc_poll/message.lib.lua b/prosody-modules/mod_muc_poll/message.lib.lua index feb94b42..0a1b8850 100644 --- a/prosody-modules/mod_muc_poll/message.lib.lua +++ b/prosody-modules/mod_muc_poll/message.lib.lua @@ -3,10 +3,18 @@ local id = require "util.id"; local st = require "util.stanza"; -local format = require"util.format".format; +local timer = require "util.timer"; local xmlns_occupant_id = "urn:xmpp:occupant-id:0"; +local xmlns_replace = "urn:xmpp:message-correct:0"; -local function build_poll_message(room, message_id) +local mod_muc = module:depends"muc"; +local get_room_from_jid = mod_muc.get_room_from_jid; + +local debounce_delay = 10; -- number of seconds during which we must group votes to avoid flood. +local scheduled_updates = {}; + +-- construct the poll message stanza +local function build_poll_message(room, message_id, is_end_message) local current_poll = room._data.current_poll; if not current_poll then return nil; @@ -15,6 +23,10 @@ local function build_poll_message(room, message_id) local content = current_poll["muc#roompoll_question"] .. "\n"; + if is_end_message then + content = content .. "This poll is now over.\n"; + end + local total = 0; for choice, nb in pairs(current_poll.votes_by_choices) do total = total + nb; @@ -23,12 +35,15 @@ local function build_poll_message(room, message_id) content = content .. choice .. ': ' .. label; if total > 0 then local nb = current_poll.votes_by_choices[choice] or 0; - local percent = format("%d.%d%d", nb * 100 / total); + local percent = string.format("%.2f", nb * 100 / total); content = content .. " (" .. nb .. "/" .. total .. " = " .. percent .. "%)"; end content = content .. "\n"; end - content = content .. "Send a message with an exclamation mark followed by your choice number to vote. Example: !1\n"; + + if not is_end_message then + content = content .. "Send a message with an exclamation mark followed by your choice number to vote. Example: !1\n"; + end local msg = st.message({ type = "groupchat", @@ -44,32 +59,67 @@ local function build_poll_message(room, message_id) return msg; end +-- sends a message when the poll starts. local function poll_start_message(room) if not room._data.current_poll then return nil; end module:log("debug", "Sending the start message for room %s poll", room.jid); local message_id = id.medium(); - local msg = build_poll_message(room, message_id); + local msg = build_poll_message(room, message_id, false); room:broadcast_message(msg); return message_id; end -local function schedule_poll_update_message(room) - -- TODO +-- Send the poll update message +local function send_poll_update_message(room) + if not room._data.current_poll then + return nil; + end + module:log("debug", "Sending an update message for room %s poll", room.jid); + local message_id = id.medium(); -- generate a new id + local msg = build_poll_message(room, message_id, false); - -- if not room._data.current_poll then - -- return nil; - -- end - -- module:log("debug", "Sending an update message for room %s poll", room.jid); - -- local message_id = id.medium(); - -- local msg = build_poll_message(room, message_id); - -- room:broadcast_message(msg); - -- return message_id; + -- the update message is a message (see XEP-0308). + msg:tag('replace', { + xmlns = xmlns_replace; + id = room._data.current_poll.start_message_id; + }):up(); + + room:broadcast_message(msg); + return message_id; end +-- Schedule an update of the start message. +-- We do not send this update each time someone vote, +-- to avoid flooding. +local function schedule_poll_update_message(room_jid) + if scheduled_updates[room_jid] then + -- already a running timer, we can ignore to debounce. + return; + end + scheduled_updates[room_jid] = timer.add_task(debounce_delay, function() + scheduled_updates[room_jid] = nil; + -- We dont pass room, because here it could have been removed from memory. + -- So we must relad the room from the JID in any case. + local room = get_room_from_jid(room_jid); + if not room then + return; + end + send_poll_update_message(room); + end); +end + +-- Send a new message when the poll is over, with the result. local function poll_end_message(room) - -- TODO + if not room._data.current_poll then + return nil; + end + module:log("debug", "Sending the end message for room %s poll", room.jid); + local message_id = id.medium(); -- generate a new id + local msg = build_poll_message(room, message_id, true); + room:broadcast_message(msg); + return message_id; end return { diff --git a/prosody-modules/mod_muc_poll/poll.lib.lua b/prosody-modules/mod_muc_poll/poll.lib.lua index 6d15a8bd..3af17acd 100644 --- a/prosody-modules/mod_muc_poll/poll.lib.lua +++ b/prosody-modules/mod_muc_poll/poll.lib.lua @@ -56,7 +56,9 @@ local function end_current_poll (room) timer.stop(scheduled_end[room_jid]); scheduled_end[room_jid] = nil; end + poll_end_message(room); + -- TODO: store the result somewhere, to keep track? -- We don't remove the poll immediatly. Indeed, if the vote is anonymous, @@ -173,18 +175,18 @@ local function handle_groupchat(event) module:log("debug", "Counting a new vote for room %s: choice %i, voter %s", room.jid, choice, occupant_bare_id); -- counting the vote: if room._data.current_poll.votes_by_occupant[occupant_bare_id] ~= nil then - module:log("debug", "Occupant %s has already voted for current room %s vote, reassigning his vote.", occupant_bare_id); + module:log("debug", "Occupant %s has already voted for current room %s vote, reassigning his vote.", occupant_bare_id, room.jid); room._data.current_poll.votes_by_choices[room._data.current_poll.votes_by_occupant[occupant_bare_id]] = room._data.current_poll.votes_by_choices[room._data.current_poll.votes_by_occupant[occupant_bare_id]] - 1; end room._data.current_poll.votes_by_choices[choice] = room._data.current_poll.votes_by_choices[choice] + 1; room._data.current_poll.votes_by_occupant[occupant_bare_id] = choice; - schedule_poll_update_message(room); + schedule_poll_update_message(room.jid); -- When the poll is anonymous, we bounce the messages (but count the votes). local must_bounce = room._data.current_poll["muc#roompoll_anonymous"] == true; if must_bounce then - module:log("debug", "Invalid vote, bouncing it."); + module:log("debug", "Anonymous votes, bouncing it."); origin.send(st.error_reply( stanza, -- error_type @@ -218,7 +220,7 @@ local function room_restored(event) schedule_poll_end(room.jid, room._data.current_poll.end_timestamp); end -- just in case, we can also reschedule an update message - schedule_poll_update_message(room); + schedule_poll_update_message(room.jid); end return {