214 lines
6.7 KiB
Python
Executable File
214 lines
6.7 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""
|
|
Script to monitor Matrix chat for !getvotes command and post vote results.
|
|
Reads from voteServer/vote_summary.json and posts results ordered from least to most votes.
|
|
"""
|
|
|
|
import asyncio
|
|
import json
|
|
import os
|
|
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...")
|
|
|
|
# Clear the web poll by marking all pending suggestions as posted
|
|
room.mark_submissions_posted()
|
|
|
|
# Read and format vote results
|
|
vote_message = format_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
|
|
|
|
|
|
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 = f"votes_{safe}.db"
|
|
|
|
# Check if database exists
|
|
if not os.path.exists(vote_db):
|
|
return "❌ No vote data available yet. The voting system may not have received any votes."
|
|
|
|
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 votes have been cast yet."
|
|
|
|
# Build the message
|
|
lines = ["📊 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 vote data: {str(e)}"
|
|
|
|
|
|
if __name__ == "__main__":
|
|
asyncio.run(main())
|