Add anonymous multi-user vote command - #191
Conversation
Allow Admin/CM/Community Team to start timed anonymous polls so staff can approve or reject nominees without revealing voter identity or live tallies until close. Co-authored-by: Cursor <cursoragent@cursor.com>
|
can't code so had to use cursor. This is to help FalconSpy with our CC program via discord to keep moderation team votes anonymous. PLEASE ASSESS AND VERIFY CODE BEFORE PUSHING TO MAIN. |
|
@MetaspIoit I've reviewed the PR and got some comments. Do you prefer me to post them, so you can try to fix them, or do you prefer me to take over? |
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #191 +/- ##
==========================================
+ Coverage 66.54% 69.79% +3.25%
==========================================
Files 54 56 +2
Lines 3177 3536 +359
==========================================
+ Hits 2114 2468 +354
- Misses 1063 1068 +5 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
Concurrency and correctness fixes on top of the initial implementation, plus tests for the command and the view. Voting: - Read the chosen nominee from each interaction's own payload. py-cord shares the Select item across all voters and overwrites its state per interaction while callbacks run in later tasks, so two voters could cross nominees. - Move Approve/Reject onto a private per-voter ballot, replacing a module-level dict of pending selections that was lost on restart. - Re-check VOTE_CASTERS when a ballot is cast, not only when it is issued, so a voter whose role is revoked mid-poll cannot still cast. - Give the ballot an on_timeout. A finished view is evicted from the ViewStore, so a later click received no response at all and Discord showed 'This interaction failed' on buttons that still looked live. - Defer before database work in every callback; AsyncSessionLocal uses NullPool and Discord's initial-response deadline is 3 seconds. Persistence: - Write ballots as a single upsert so a double-click cannot race uq_anonymous_vote_ballot_session_candidate_voter. - Claim the close with a conditional UPDATE. The previous read-check-write straddled two awaits, and on_ready reschedules a close on every reconnect, so a long-running vote could publish its results more than once. - Store closes_at as BIGINT epoch seconds, matching Ban.unban_time, instead of a TIMESTAMP that caps at 2038 and round-trips through session timezones. - Delete the session row when the poll cannot be posted, rather than leaving it orphaned with no message id and no scheduled close. Input handling: - Bound topic to 200 characters. build_results_embed prefixes 'Results: ', so a longer topic exceeded the 256-character embed title limit and failed at close time, losing the tallies. - Cap vote duration at 30 days and require nominee IDs to be snowflake-shaped. Adds 72 tests covering the command and the view, including the approve/reject tally, the activity-box truncation boundary, and the concurrency guards.
|
Hi @MetaspIoit — thanks for this, the feature and the data model are a good shape and I wanted it to land. I've pushed a commit to your branch ( Concurrency
Discord API constraints
Storage
TestsAdded 72 tests for the command and the view. Worth flagging one thing I got wrong first time: the tally logic ( Two things that were your calls, not mineI'd rather ask than quietly redesign, and I'm happy to revert either. 1. Approve/Reject moved from public buttons to a private per-voter ballot. The crossing-nominees bug only strictly required reading from 2. Vote duration is capped at 30 days. Nothing forced this — One note on the test planThe checklist in the description now describes the old flow. If you're working through it: step 4 gives you a private ballot rather than buttons on the poll message, and there are two new paths worth a look — Known limitation, unchanged from your designWith a small pool of eligible voters, the live per-nominee activity boxes leak participation — you can tell how many people voted on each nominee, though not which way. The approve/reject split stays hidden until close. I left this as you built it; say the word if you'd prefer an aggregate count or nothing at all until close. |
Summary
/admin votefor Administrator, Community Manager, and Community Team to start a timed anonymous poll over multiple Discord membersTest plan
alembic upgrade head)/admin voteappears for Admin/CM/CT only2m)Made with Cursor