From 4d92925b3b1651fca057ca50e47d96762b47df6b Mon Sep 17 00:00:00 2001 From: Stan Soldatov <118521851+iwatkot@users.noreply.github.com> Date: Mon, 4 Nov 2024 20:01:29 +0100 Subject: [PATCH] Statistics for bot * Statistics for bot. * Stats file. --- .github/workflows/build_bot.yml | 3 +- bot/bot.py | 95 ++++++++++++++++++++++++++++++++- bot/bot_templates.py | 3 ++ 3 files changed, 98 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build_bot.yml b/.github/workflows/build_bot.yml index f25b061..8126af8 100644 --- a/.github/workflows/build_bot.yml +++ b/.github/workflows/build_bot.yml @@ -26,4 +26,5 @@ jobs: tags: iwatkot/maps4fsbot:latest file: ./docker/Dockerfile_bot build-args: | - BOT_TOKEN=${{ secrets.BOT_TOKEN }} \ No newline at end of file + BOT_TOKEN=${{ secrets.BOT_TOKEN }} + ADMIN_ID=${{ secrets.ADMIN_ID }} \ No newline at end of file diff --git a/bot/bot.py b/bot/bot.py index da7c33d..3e7fee9 100644 --- a/bot/bot.py +++ b/bot/bot.py @@ -32,17 +32,26 @@ map_template = os.path.join(working_directory, "data", "map-template.zip") maps_directory = os.path.join(working_directory, "maps") archives_directory = os.path.join(working_directory, "archives") +stats_directory = os.path.join(working_directory, "stats") +stats_file = os.path.join(stats_directory, "stats.txt") # endregion os.makedirs(maps_directory, exist_ok=True) os.makedirs(archives_directory, exist_ok=True) logger.info("Working directory: %s", working_directory) +os.makedirs(stats_directory, exist_ok=True) +logger.info("Stats directory: %s", stats_directory) + # region environment variables env_path = os.path.join(working_directory, "bot.env") if os.path.exists(env_path): load_dotenv(env_path) token = os.getenv("BOT_TOKEN") +admin_id = os.getenv("ADMIN_ID") +if admin_id: + admin_id = int(admin_id) +logger.info("Admin ID: %s", admin_id) if not token: raise RuntimeError("No token provided.") # endregion @@ -134,6 +143,43 @@ async def button_github(message: types.Message) -> None: ) +@dp.message_handler(Text(equals=Buttons.STATISTICS.value)) +async def button_statistics(message: types.Message) -> None: + """Handles the Statistics button. + + Args: + message (types.Message): Message, which triggered the handler. + """ + await log_event(message) + + try: + with open(stats_file, "r") as file: + lines = file.readlines() + stats = len(lines) + except FileNotFoundError: + stats = 0 + + await bot.send_message( + message.from_user.id, + Messages.STATISTICS.value.format(stats), + reply_markup=await keyboard(Buttons.MAIN_MENU.value), + parse_mode=types.ParseMode.MARKDOWN_V2, + ) + + if admin_id and admin_id == message.from_user.id: + logger.info("Admin requested stats.") + if not os.path.isfile(stats_file): + logger.info("No stats file found.") + return + # Send the stats file to the admin. + try: + admin_stats = types.InputFile(stats_file) + await bot.send_document(admin_id, admin_stats) + logger.info("Stats file sent to the admin.") + except Exception as e: + logger.error(f"Error during sending stats file to the admin: {repr(e)}") + + @dp.message_handler(Text(equals=Buttons.COFFEE.value)) async def button_coffee(message: types.Message) -> None: """Handles the Buy me a coffee button. @@ -288,20 +334,30 @@ async def max_height_callback(callback_query: types.CallbackQuery) -> None: reply_markup=await keyboard(Buttons.MAIN_MENU.value), ) - preview, result = session.run() + try: + preview, result = session.run() + except Exception as e: + logger.error(f"Error during generation: {repr(e)}") + return archive = types.InputFile(result) picture = types.InputFile(preview) await bot.send_photo(callback_query.from_user.id, picture) try: await bot.send_document(callback_query.from_user.id, archive) except Exception as e: - logger.error(e) + logger.error(repr(e)) await bot.send_message( callback_query.from_user.id, Messages.FILE_TOO_LARGE.value, reply_markup=await keyboard(Buttons.MAIN_MENU.value), ) + try: + await save_stats(session, "success") + await notify_admin(session, "success") + except Exception as e: + logger.error(f"Error during saving stats and/or notifying admin: {repr(e)}") + try: os.remove(preview) os.remove(result) @@ -309,6 +365,41 @@ async def max_height_callback(callback_query: types.CallbackQuery) -> None: logger.error(e) +async def save_stats(session: Session, status: str) -> None: + """Saves the stats of the session to text file. + + Args: + session (Session): Session to save. + status (str): Result of the session. + """ + entry = ( + f"{session.timestamp} {session.telegram_id} {session.coordinates} " + f"{session.distance} {session.max_height}\n" + ) + with open(stats_file, "a") as file: + file.write(entry) + + logger.info("Stats saved for %s", session.name) + + +async def notify_admin(session: Session, status: str) -> None: + """Notifies the admin about the session. + + Args: + session (Session): Session to notify about. + status (str): Result of the session. + """ + if not admin_id: + logger.info("No admin ID provided, skipping notification.") + return + await bot.send_message( + admin_id, + f"Session started by {session.telegram_id} at {session.timestamp}.", + ) + + logger.info("Admin notified about %s", session.name) + + async def keyboard( buttons: list[str] | dict[str, str] ) -> ReplyKeyboardMarkup | InlineKeyboardMarkup: diff --git a/bot/bot_templates.py b/bot/bot_templates.py index 2650128..b74b529 100644 --- a/bot/bot_templates.py +++ b/bot/bot_templates.py @@ -43,6 +43,8 @@ class Messages(Enum): GENERATION_STARTED = "Map generation has been started. It may take a while." FILE_TOO_LARGE = "The map is too large to send it via Telegram. Please, lower the map size." + STATISTICS = "Recently was generated {} maps." + class Buttons(Enum): """Buttons, which are used in the bot menu.""" @@ -50,6 +52,7 @@ class Buttons(Enum): GENERATE = "πŸ—ΊοΈ Generate new map" GITHUB = "πŸ™ Open on GitHub" COFFEE = "β˜• Buy me a coffee" + STATISTICS = "πŸ“Š Statistics" MAIN_MENU = [GENERATE, GITHUB, COFFEE] CANCEL = "❌ Cancel"