Skip to content

Commit

Permalink
saving pols to db (#15)
Browse files Browse the repository at this point in the history
* pols saving to db (previously was stored in memory), can save pols after reload

* setting TZ to GMT by default
  • Loading branch information
Alex-Kopylov authored Mar 3, 2024
1 parent ca180c2 commit 85041d8
Show file tree
Hide file tree
Showing 11 changed files with 245 additions and 132 deletions.
3 changes: 2 additions & 1 deletion docker-compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ services:
- SQLITE_DB_FILE_PATH=/database/db.sqlite
- TELEGRAM_BOT_TOKEN=${TELEGRAM_BOT_TOKEN}
- DEVELOPER_CHAT_ID=${DEVELOPER_CHAT_ID}
- TZ=GMT
volumes:
- sqlite_data:/database
entrypoint: ["/bin/sh", "-c"]
Expand All @@ -23,7 +24,7 @@ services:
environment:
- PUID=1000
- PGID=1000
- TZ=GMT+4
- TZ=GMT
- ENABLE_UWSGI=true
volumes:
- sqlite_data:/database
Expand Down
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 85041d8

Please sign in to comment.