diff --git a/lib/poll.js b/lib/poll.js index e9bb8a0..7067974 100755 --- a/lib/poll.js +++ b/lib/poll.js @@ -202,7 +202,16 @@ Poll.getInfo = async function (pollId, withVotes = false) { if (!poll) { return null; } - poll.voteCount = parseInt(voteCount, 10) || 0; + + // Check for vote counts override (e.g., from remote AP Question) + const override = await Poll.getVoteCountsOverride(pollId); + if (override) { + poll.voteCount = override.total || 0; + poll.uniqueVoters = override.uniqueVoters || null; + } else { + poll.voteCount = parseInt(voteCount, 10) || 0; + } + const end = parseInt(poll.end, 10); poll.ended = end > 0 && Date.now() > end; poll.options = await loadOptions(pollId, poll.options, withVotes); @@ -222,6 +231,18 @@ Poll.getPollOptionIds = async function (pollId) { async function loadOptions(pollId, optionsJson, withVotes = false) { const options = tryParseOptions(optionsJson); + // Check for vote counts override (e.g., from remote AP Question) + const override = await Poll.getVoteCountsOverride(pollId); + if (override) { + options.forEach((option, index) => { + if (option) { + const ov = override.options?.find(o => String(o.id) === String(option.id)); + option.voteCount = ov?.voteCount || 0; + } + }); + return options; + } + const votes = await db.getSortedSetsMembers(options.map(o => `poll:${pollId}:options:${o.id}:votes`)); options.forEach((option, index) => { @@ -246,19 +267,29 @@ function tryParseOptions(optionsJson) { } Poll.getOption = async function (pollId, option, withVotes = false) { - const [optionsJson, votes, voteCount] = await Promise.all([ - db.getObjectField(`poll:${pollId}`, 'options'), - withVotes ? - db.getSortedSetRange(`poll:${pollId}:options:${option}:votes`, 0, -1) : - null, - Poll.getOptionVoteCount(pollId, option), - ]); + const optionsJson = await db.getObjectField(`poll:${pollId}`, 'options'); const options = tryParseOptions(optionsJson); const optionData = options.find(opt => String(opt.id) === String(option)); if (!optionData) { return null; } + // Check for vote counts override (e.g., from remote AP Question) + const override = await Poll.getVoteCountsOverride(pollId); + if (override) { + const ov = override.options?.find(o => String(o.id) === String(option)); + optionData.voteCount = ov?.voteCount || 0; + // No voter identity data available for override polls + return optionData; + } + + const [votes, voteCount] = await Promise.all([ + withVotes ? + db.getSortedSetRange(`poll:${pollId}:options:${option}:votes`, 0, -1) : + null, + Poll.getOptionVoteCount(pollId, option), + ]); + if (votes) { optionData.votes = votes; } @@ -267,10 +298,20 @@ Poll.getOption = async function (pollId, option, withVotes = false) { }; Poll.getVotersCount = async function (pollId) { + // Check for vote counts override (e.g., from remote AP Question) + const override = await Poll.getVoteCountsOverride(pollId); + if (override) { + return override.uniqueVoters || null; + } return await db.sortedSetCard(`poll:${pollId}:voters`); }; Poll.getVoteCount = async function (pollId) { + // Check for vote counts override (e.g., from remote AP Question) + const override = await Poll.getVoteCountsOverride(pollId); + if (override) { + return override.total || 0; + } const optionIds = await Poll.getPollOptionIds(pollId); return await db.sortedSetsCardSum( optionIds.map(option => `poll:${pollId}:options:${option}:votes`) @@ -278,9 +319,36 @@ Poll.getVoteCount = async function (pollId) { }; Poll.getOptionVoteCount = async function (pollId, option) { + // Check for vote counts override (e.g., from remote AP Question) + const override = await Poll.getVoteCountsOverride(pollId); + if (override) { + const ov = override.options?.find(o => String(o.id) === String(option)); + return ov?.voteCount || 0; + } return await db.sortedSetCard(`poll:${pollId}:options:${option}:votes`); }; +/** + * Returns the vote counts override object if set on the poll, otherwise null. + * The override is stored as JSON in the `voteCounts` field of the poll hash. + * Expected shape: { total: number, uniqueVoters: number, options: [{ id, voteCount }] } + */ +Poll.getVoteCountsOverride = async function (pollId) { + const raw = await db.getObjectField(`poll:${pollId}`, 'voteCounts'); + if (!raw) { + return null; + } + try { + const parsed = JSON.parse(raw); + if (typeof parsed.total === 'number' && Array.isArray(parsed.options)) { + return parsed; + } + return null; + } catch { + return null; + } +}; + Poll.hasOption = async function (pollId, option) { const optionIds = new Set(await Poll.getPollOptionIds(pollId)); return optionIds.has(String(option));