Files
titlebot-ng-ng/get_votes.py
T
2026-10-31 01:12:01 -05:00

219 lines
6.9 KiB
Python
Executable File

#!/usr/bin/env python3
"""
Script to monitor Matrix chat for !getvotes command and post vote results.
Reports current emoji votes and separately labeled web votes.
"""
import asyncio
import json
import os
from pathlib import Path
import sys
import time as _time
from room import Room
async def main():
"""Main function to monitor for !getvotes command and post results."""
# Initialize room connection
config_path = sys.argv[1] if len(sys.argv) > 1 else "configs/config.yaml"
room = Room(config_path=config_path)
# Login to Matrix
if not await room.login_to_matrix():
print("Failed to login to Matrix")
sys.exit(1)
print("Logged in and monitoring for !getvotes command...")
print("Press Ctrl+C to exit")
# Add getvotes table to database if it doesn't exist
init_getvotes_table(room)
# Start listening for commands from "now"
since_ms = int(_time.time() * 1000)
try:
while True:
# Wait for !getvotes command
command = await wait_for_getvotes(room, since_ms)
if command:
print("!getvotes command detected, posting vote results...")
# Read and format vote results
vote_message = await get_vote_results(room)
# Post to chat
await room.alert_post(vote_message)
print("Vote results posted successfully")
except KeyboardInterrupt:
print("\nShutting down...")
finally:
await room.close()
def init_getvotes_table(room):
"""Initialize the getvotes tracking table in the database."""
import dbutil
conn = dbutil.connect(room.db_path)
c = conn.cursor()
c.execute('''
CREATE TABLE IF NOT EXISTS getvotes (
id INTEGER PRIMARY KEY AUTOINCREMENT,
event_id TEXT,
sender TEXT,
timestamp INTEGER,
datetime TEXT,
UNIQUE(event_id),
UNIQUE(sender, timestamp)
)
''')
dbutil.commit(conn)
conn.close()
def get_last_getvotes_timestamp(room):
"""Get the timestamp of the last !getvotes command for this room."""
import dbutil
conn = dbutil.connect(room.db_path)
c = conn.cursor()
c.execute('''
SELECT MAX(timestamp) FROM getvotes
''')
row = c.fetchone()
conn.close()
if row and row[0]:
return row[0]
return None
def record_getvotes(room, sender, timestamp, event_id=None):
"""Record a !getvotes detection; returns True if newly recorded (not duplicate)."""
import dbutil
import datetime
conn = dbutil.connect(room.db_path)
c = conn.cursor()
dt_str = datetime.datetime.fromtimestamp(int(timestamp)).isoformat(sep=' ', timespec='seconds')
c.execute('''
INSERT OR IGNORE INTO getvotes (event_id, sender, timestamp, datetime)
VALUES (?, ?, ?, ?)
''', (event_id, sender, int(timestamp), dt_str))
inserted = c.rowcount == 1
dbutil.commit(conn)
conn.close()
return inserted
async def wait_for_getvotes(room, since_ms):
"""Wait for !getvotes command in the chat."""
from nio import SyncResponse, RoomMessageText
sync_response = await room.client.sync(timeout=room.config.command_sync_timeout)
if not isinstance(sync_response, SyncResponse):
return None
room_info = sync_response.rooms.join.get(room.config.room_id)
if not room_info or not hasattr(room_info, "timeline") or not hasattr(room_info.timeline, "events"):
return None
for event in reversed(room_info.timeline.events):
if isinstance(event, RoomMessageText):
# Ignore old events (pre-listen timestamp)
if getattr(event, "server_timestamp", 0) < since_ms:
continue
# Ignore our own messages
if event.sender == room.user_id:
continue
body = event.body.strip()
# Handle !getvotes
if body == "!getvotes":
event_id = getattr(event, "event_id", None)
ts_seconds = int(getattr(event, "server_timestamp", 0) // 1000)
if not record_getvotes(room, event.sender, ts_seconds, event_id):
continue # Duplicate, skip
return "getvotes"
return None
async def get_vote_results(room):
"""Report emoji and web votes separately; they are independent ballots."""
emoji_results = await room.count_votes()
web_results = format_vote_results(room)
return f"{emoji_results}\n\n{web_results}"
def format_vote_results(room):
"""Read votes from database and format results ordered from least to most votes."""
import re
# Generate database name based on room ID (same pattern as suggestions db)
safe = re.sub(r'[^a-zA-Z0-9_-]', '_', room.config.room_id)
vote_db = Path(__file__).resolve().parent / f"votes_{safe}.db"
# Check if database exists
if not os.path.exists(vote_db):
return "📊 No web-vote database available yet. Emoji votes are reported separately above."
try:
import dbutil
# Read votes from database
conn = dbutil.connect(vote_db)
c = conn.cursor()
# Get all votes grouped by submission
c.execute('''
SELECT selected_submission, COUNT(*) as vote_count,
GROUP_CONCAT(voter_display_name, ', ') as voters
FROM votes
GROUP BY selected_submission
ORDER BY vote_count ASC
''')
results = c.fetchall()
# Get total votes
c.execute('SELECT COUNT(*) FROM votes')
total_votes = c.fetchone()[0]
conn.close()
if not results or total_votes == 0:
return "📊 No web votes have been cast yet. Emoji votes are reported separately above."
# Build the message
lines = ["📊 Web Vote Results (ordered from least to most votes):", ""]
for submission, count, voters in results:
vote_word = "vote" if count == 1 else "votes"
lines.append(f"• {submission}: {count} {vote_word}")
lines.append("")
lines.append(f"Total votes cast: {total_votes}")
# Find winner(s)
max_votes = results[-1][1] if results else 0
winners = [sub for sub, count, _ in results if count == max_votes]
if max_votes > 0:
if len(winners) > 1:
lines.append(f"🏆 Tie between {len(winners)} submissions with {max_votes} votes each!")
else:
lines.append(f"🏆 Current leader: {winners[0]} ({max_votes} votes)")
return "\n".join(lines)
except Exception as e:
return f"❌ Error reading web vote data: {str(e)}"
if __name__ == "__main__":
asyncio.run(main())