Poll WIP (#231):

* poll backend WIP
This commit is contained in:
John Livingston 2024-06-30 17:57:33 +02:00
parent 212076c3a3
commit b741959312
No known key found for this signature in database
GPG Key ID: B17B5640CE66CDBC
2 changed files with 72 additions and 20 deletions

View File

@ -3,10 +3,18 @@
local id = require "util.id"; local id = require "util.id";
local st = require "util.stanza"; 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_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; local current_poll = room._data.current_poll;
if not current_poll then if not current_poll then
return nil; return nil;
@ -15,6 +23,10 @@ local function build_poll_message(room, message_id)
local content = current_poll["muc#roompoll_question"] .. "\n"; 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; local total = 0;
for choice, nb in pairs(current_poll.votes_by_choices) do for choice, nb in pairs(current_poll.votes_by_choices) do
total = total + nb; total = total + nb;
@ -23,12 +35,15 @@ local function build_poll_message(room, message_id)
content = content .. choice .. ': ' .. label; content = content .. choice .. ': ' .. label;
if total > 0 then if total > 0 then
local nb = current_poll.votes_by_choices[choice] or 0; 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 .. "%)"; content = content .. " (" .. nb .. "/" .. total .. " = " .. percent .. "%)";
end end
content = content .. "\n"; content = content .. "\n";
end 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({ local msg = st.message({
type = "groupchat", type = "groupchat",
@ -44,32 +59,67 @@ local function build_poll_message(room, message_id)
return msg; return msg;
end end
-- sends a message when the poll starts.
local function poll_start_message(room) local function poll_start_message(room)
if not room._data.current_poll then if not room._data.current_poll then
return nil; return nil;
end end
module:log("debug", "Sending the start message for room %s poll", room.jid); module:log("debug", "Sending the start message for room %s poll", room.jid);
local message_id = id.medium(); 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); room:broadcast_message(msg);
return message_id; return message_id;
end end
local function schedule_poll_update_message(room) -- Send the poll update message
-- TODO 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 -- the update message is a <replace> message (see XEP-0308).
-- return nil; msg:tag('replace', {
-- end xmlns = xmlns_replace;
-- module:log("debug", "Sending an update message for room %s poll", room.jid); id = room._data.current_poll.start_message_id;
-- local message_id = id.medium(); }):up();
-- local msg = build_poll_message(room, message_id);
-- room:broadcast_message(msg); room:broadcast_message(msg);
-- return message_id; return message_id;
end 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) 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 end
return { return {

View File

@ -56,7 +56,9 @@ local function end_current_poll (room)
timer.stop(scheduled_end[room_jid]); timer.stop(scheduled_end[room_jid]);
scheduled_end[room_jid] = nil; scheduled_end[room_jid] = nil;
end end
poll_end_message(room); poll_end_message(room);
-- TODO: store the result somewhere, to keep track? -- TODO: store the result somewhere, to keep track?
-- We don't remove the poll immediatly. Indeed, if the vote is anonymous, -- 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); module:log("debug", "Counting a new vote for room %s: choice %i, voter %s", room.jid, choice, occupant_bare_id);
-- counting the vote: -- counting the vote:
if room._data.current_poll.votes_by_occupant[occupant_bare_id] ~= nil then 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; 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 end
room._data.current_poll.votes_by_choices[choice] = room._data.current_poll.votes_by_choices[choice] + 1; 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; 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). -- When the poll is anonymous, we bounce the messages (but count the votes).
local must_bounce = room._data.current_poll["muc#roompoll_anonymous"] == true; local must_bounce = room._data.current_poll["muc#roompoll_anonymous"] == true;
if must_bounce then if must_bounce then
module:log("debug", "Invalid vote, bouncing it."); module:log("debug", "Anonymous votes, bouncing it.");
origin.send(st.error_reply( origin.send(st.error_reply(
stanza, stanza,
-- error_type -- error_type
@ -218,7 +220,7 @@ local function room_restored(event)
schedule_poll_end(room.jid, room._data.current_poll.end_timestamp); schedule_poll_end(room.jid, room._data.current_poll.end_timestamp);
end end
-- just in case, we can also reschedule an update message -- just in case, we can also reschedule an update message
schedule_poll_update_message(room); schedule_poll_update_message(room.jid);
end end
return { return {