Skip to content

Commit

Permalink
pols saving to db (previously was stored in memory), can save pols af…
Browse files Browse the repository at this point in the history
…ter reload
  • Loading branch information
Alex-Kopylov committed Mar 2, 2024
1 parent ca180c2 commit 28c38e1
Show file tree
Hide file tree
Showing 10 changed files with 243 additions and 131 deletions.
43 changes: 43 additions & 0 deletions src/callbacks/poll_callback_receiver.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import logging

from telegram import Update
from telegram.ext import ContextTypes

from src.data_models.Player import Player
from src.data_models.PollResult import PollResult
from src.services.db_service import save_player, save_poll_result, delete_poll_result


async def poll_callback_receiver(
update: Update, context: ContextTypes.DEFAULT_TYPE
) -> None:
"""Save or delete poll results in the database."""
answer = update.poll_answer
user_id = update.effective_user.id
poll_id = int(answer.poll_id) # Convert from telegram string id to int

if not answer.option_ids: # Poll retraction, delete previous vote
await delete_poll_result(poll_id=poll_id, user_id=user_id)
return

# Retrieve the selected option's text or index
selected_option_index = answer.option_ids[0]

await save_poll_result(
PollResult(
poll_id=poll_id,
user_id=user_id,
answer=selected_option_index,
)
)
await save_player(
Player(
telegram_user_id=user_id,
username=update.effective_user.username,
first_name=update.effective_user.first_name,
full_name=update.effective_user.full_name,
last_name=update.effective_user.last_name,
is_bot=update.effective_user.is_bot,
language_code=update.effective_user.language_code,
)
)
40 changes: 0 additions & 40 deletions src/callbacks/receive_poll_answer.py

This file was deleted.

11 changes: 8 additions & 3 deletions src/data_models/Game.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,23 @@
from pydantic import BaseModel, field_validator

from src import config
from src.data_models.PollResult import PollResult


class Game(BaseModel):
poll_id: int
chat_id: int
results: dict # Literal["Hitler Canceler", "Fascist Law", "Hitler Death", "Liberal Law"]
results: tuple[
PollResult, ...
] # Literal["Hitler Canceler", "Fascist Law", "Hitler Death", "Liberal Law"]
creator_id: int

@field_validator("results", mode="after")
@classmethod
def validate_results(cls, v: dict) -> Literal["CH", "DH", "FW", "LW"]:
outcomes = set(v.values())
def validate_results(
cls, results: tuple[PollResult]
) -> Literal["CH", "DH", "FW", "LW"]:
outcomes = set(outcome.get_answer_as_text() for outcome in results)
if "I'm Canceler Hitler" in outcomes:
return "CH"
if "I'm Dead Hitler" in outcomes:
Expand Down
5 changes: 3 additions & 2 deletions src/data_models/Poll.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@


class Poll(BaseModel):
poll_id: int
id: int
message_id: int
chat_id: int
chat_name: str
creator_id: int
poll_type: Literal["default_game"] = "default"
creator_username: str
12 changes: 12 additions & 0 deletions src/data_models/PollResult.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
from pydantic import BaseModel, Field

from src.config import AppConfig


class PollResult(BaseModel):
poll_id: int
user_id: int
answer: int = Field(ge=0, le=len(AppConfig().game_poll_outcomes))

def get_answer_as_text(self) -> str:
return AppConfig().game_poll_outcomes[self.answer]
4 changes: 2 additions & 2 deletions src/get_handlers.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from telegram.ext import PollAnswerHandler, CommandHandler

from src import handlers
from src.callbacks.receive_poll_answer import receive_poll_answer
from src.callbacks.poll_callback_receiver import poll_callback_receiver


def get_handlers() -> tuple:
Expand All @@ -12,5 +12,5 @@ def get_handlers() -> tuple:
CommandHandler("game", handlers.game),
CommandHandler("save", handlers.save),
# Poll answer handler
PollAnswerHandler(receive_poll_answer),
PollAnswerHandler(poll_callback_receiver),
)
51 changes: 26 additions & 25 deletions src/handlers/game.py
Original file line number Diff line number Diff line change
@@ -1,46 +1,47 @@
import asyncio

from telegram import Update
from telegram.ext import ContextTypes

from src import config
from src.data_models.Poll import Poll
from src.data_models.Playroom import Playroom
from src.services.db_service import save_playroom
from src.services.db_service import save_playroom, save_poll
from src.config import AppConfig


async def game(
update: Update, context: ContextTypes.DEFAULT_TYPE, config: AppConfig = AppConfig()
) -> None:
"""Sends a predefined poll"""
"""Sends a predefined poll and saves its metadata to the database."""

questions = config.game_poll_outcomes

message = await context.bot.send_poll(
update.effective_chat.id,
f"@{update.effective_user.username} want you to record last game. Please choose your outcome:",
f"@{update.effective_user.username} wants you to record the last game. Please choose your outcome:",
questions,
is_anonymous=False,
allows_multiple_answers=False,
disable_notification=True,
)

# Save some info about the poll the bot_data for later use in receive_poll_answer
game_metadata = { # TODO write it to DB
message.poll.id: {
"questions": questions,
"message_id": message.id, # will be game_id
"chat_id": update.effective_chat.id,
"chat_name": update.effective_chat.title,
"creator_id": update.effective_user.id,
"creator_username": update.effective_user.username,
"results": {},
}
}
await save_playroom(
Playroom(
telegram_chat_id=update.effective_chat.id, name=update.effective_chat.title
)
await asyncio.gather(
*[
save_poll(
Poll(
id=message.poll.id,
message_id=message.message_id, # Assuming this maps to the Poll table's message_id
chat_id=update.effective_chat.id,
chat_name=update.effective_chat.title,
creator_id=update.effective_user.id,
creator_username=update.effective_user.username,
)
),
save_playroom(
Playroom(
telegram_chat_id=update.effective_chat.id,
name=update.effective_chat.title,
)
),
update.effective_message.delete(),
]
)
context.bot_data.update(game_metadata)

# Delete command message
await update.effective_message.delete()
108 changes: 50 additions & 58 deletions src/handlers/save.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,16 @@
from telegram import Update
from telegram.ext import ContextTypes

from src.config import AppConfig
from src.data_models.Game import Game
from src.data_models.Record import Record
from src.services.db_service import (
save_record,
save_game,
fetch_poll_data,
fetch_poll_results,
)
from src.services.draw_result_image import draw_result_image
from src.services.db_service import save_record, save_game
from src.utils import message_is_poll, is_message_from_group_chat


Expand All @@ -31,86 +37,72 @@ async def _pass_checks(
"Please reply to a poll to stop and record results."
)
return False
# check reply msg is from the bot
if msg_with_poll.from_user.id != context.bot.id:

poll_data = await fetch_poll_data(msg_with_poll.poll.id)
if not poll_data:
await update.effective_message.reply_text(
f"Please reply to poll created by me @{context.bot.username}."
"Could not find the poll information in the database."
)
return False
# check user is creator of the poll
if (
update.effective_user.id
!= context.bot_data[msg_with_poll.poll.id]["creator_id"]
):

if update.effective_user.id != poll_data.creator_id:
await update.effective_message.reply_text(
f"You are not the creator of the game! "
f"Only @{context.bot_data[msg_with_poll.poll.id]['creator_username']} can stop this poll."
f"You are not the creator of the game! Only @{poll_data['creator_username']} can stop this poll."
)
return False
# check the poll is in the same chat as the reply msg
if update.effective_chat.id != context.bot_data[msg_with_poll.poll.id]["chat_id"]:

if update.effective_chat.id != poll_data.chat_id:
await update.effective_message.reply_text(
f"You can only save game in a group chat where other players can see the results."
"You can only save the game in the group chat where the poll was created."
)
return False

return True


async def save(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
"""Saves a game
Replying /save to the poll message (created by the bot) stops the poll
It can only be done by the creator of the poll
Save poll results
"""

msg_with_poll = (
update.effective_message.reply_to_message
) # get a poll from reply message
async def save(
update: Update, context: ContextTypes.DEFAULT_TYPE, config: AppConfig = AppConfig()
) -> None:
msg_with_poll = update.effective_message.reply_to_message
if await _pass_checks(msg_with_poll=msg_with_poll, update=update, context=context):
await context.bot.stop_poll(update.effective_chat.id, msg_with_poll.id)
await context.bot.stop_poll(update.effective_chat.id, msg_with_poll.message_id)
poll_id = int(msg_with_poll.poll.id)
poll_data, poll_results = await asyncio.gather(
fetch_poll_data(poll_id), fetch_poll_results(poll_id)
)

poll_data = context.bot_data[msg_with_poll.poll.id]
records = [
Record(
creator_id=poll_data["creator_id"],
player_id=player_id,
playroom_id=poll_data["chat_id"],
game_id=poll_data["message_id"],
role=result,
creator_id=poll_data.creator_id,
player_id=results.user_id,
playroom_id=poll_data.chat_id,
game_id=poll_data.message_id,
role=results.get_answer_as_text(),
)
for player_id, result in poll_data["results"].items()
for results in poll_results
]
# await asyncio.gather(*[save_record(record) for record in records])

game = Game(
poll_id=poll_data["message_id"],
chat_id=poll_data["chat_id"],
creator_id=poll_data["creator_id"],
results=poll_data["results"].copy(),
poll_id=poll_data.message_id,
chat_id=poll_data.chat_id,
creator_id=poll_data.creator_id,
results=poll_results,
)
# post-game tasks

# Execute post-game tasks
await asyncio.gather(
*[
*[save_record(record) for record in records],
save_game(game),
context.bot.delete_message(
chat_id=game.chat_id, message_id=game.poll_id
),
update.effective_message.delete(),
context.bot.send_photo(
photo=(
await draw_result_image(
records=records,
result=game.results,
update=update,
context=context,
)
),
chat_id=game.chat_id,
caption="The Game has been saved!",
disable_notification=True,
save_game(game),
*(save_record(record) for record in records),
context.bot.delete_message(chat_id=game.chat_id, message_id=game.poll_id),
update.effective_message.delete(),
context.bot.send_photo(
chat_id=game.chat_id,
photo=await draw_result_image(
records=records, result=game.results, update=update, context=context
),
]
caption="The Game has been saved!",
disable_notification=True,
),
)
else:
await update.effective_message.reply_text(
Expand Down
Loading

0 comments on commit 28c38e1

Please sign in to comment.