From cde3dc63e98a6cce886e2d85346d2f4d53d4ff66 Mon Sep 17 00:00:00 2001 From: aaaaaaaalesha Date: Mon, 24 Apr 2023 07:56:17 +0300 Subject: [PATCH 1/6] add changes to sqlite_db.py --- src/db/sqlite_db.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/db/sqlite_db.py b/src/db/sqlite_db.py index e613728..3a081da 100644 --- a/src/db/sqlite_db.py +++ b/src/db/sqlite_db.py @@ -1,18 +1,20 @@ import os import sqlite3 +import logging + from datetime import datetime from typing import Tuple conn = sqlite3.connect('queue_bot.db') cursor = conn.cursor() +logging.basicConfig(level=logging.INFO) -def start_db() -> None: - if os.getenv('DEBUG', 'True') == 'True': - sql_file_path = 'db/init_db.sql' - else: - sql_file_path = '/app/src/db/init_db.sql' +def start_db() -> None: + debug = bool(os.getenv('DEBUG', 'True')) + logging.info(f'DEBUG={debug}') + sql_file_path = 'db/init_db.sql' if debug else '/app/src/db/init_db.sql' with open(sql_file_path, 'r') as sql_file: sql_script = sql_file.read() @@ -20,7 +22,7 @@ def start_db() -> None: conn.commit() if conn: - print("Data base has been connected!") + logging.info("Data base has been connected!") def sql_get_queue_list(admin_id_: int) -> list: From dd7cb14c370c6697072ae37d39874bd4bc1f4fc6 Mon Sep 17 00:00:00 2001 From: aaaaaaaalesha Date: Mon, 24 Apr 2023 07:58:08 +0300 Subject: [PATCH 2/6] disable gh-actions build in develop branch --- .github/workflows/main.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml index 23927eb..c352d08 100644 --- a/.github/workflows/main.yaml +++ b/.github/workflows/main.yaml @@ -2,9 +2,9 @@ name: build on: push: - branches: [ main, develop, gh-actions ] + branches: [ main, gh-actions ] pull_request: - branches: [ main, develop, gh-actions ] + branches: [ main, gh-actions ] jobs: build: From e6c79b6f84e179950bc35c106f18d0c349ef283d Mon Sep 17 00:00:00 2001 From: aaaaaaaalesha Date: Mon, 24 Apr 2023 08:06:34 +0300 Subject: [PATCH 3/6] initial changes --- requirements.txt | Bin 536 -> 538 bytes src/create_bot.py | 3 ++- src/main.py | 4 ++-- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/requirements.txt b/requirements.txt index edfe0ae19f27afd4d6409eed468e7800e6ea1f9b..c40a00333211c0864f27542ec069266f6d3dfe0f 100644 GIT binary patch delta 14 VcmbQiGK*!x5oSXMqm4&{8381n1it_P delta 12 TcmbQmGJ|Ep5k||6$AcLG9zz6` diff --git a/src/create_bot.py b/src/create_bot.py index e40f79b..6092942 100644 --- a/src/create_bot.py +++ b/src/create_bot.py @@ -3,12 +3,13 @@ from dotenv import load_dotenv -from aiogram import Bot, Dispatcher, types +from aiogram import Bot, Dispatcher from aiogram.contrib.fsm_storage.memory import MemoryStorage load_dotenv('.env') # Configure logging +logger = logging.getLogger() logging.basicConfig(level=logging.INFO) # Initialize storage for FSM. diff --git a/src/main.py b/src/main.py index 0de6e71..a9b8f41 100644 --- a/src/main.py +++ b/src/main.py @@ -1,12 +1,12 @@ from aiogram import executor -from create_bot import dp +from create_bot import dp, logger from handlers import admin, client, shared from db import sqlite_db async def on_startup(_) -> None: - print("Bot is online!") + logger.info("Bot is online!") sqlite_db.start_db() From 89d34c247288589fa0871bcccb40b45748760821 Mon Sep 17 00:00:00 2001 From: aaaaaaaalesha Date: Mon, 24 Apr 2023 17:58:01 +0300 Subject: [PATCH 4/6] some codestyle changes --- src/__init__.py | 2 - src/{main.py => __main__.py} | 8 +- src/db/sqlite_db.py | 76 ++++++++++++----- src/handlers/admin.py | 147 +++++++++++++++++++++++++-------- src/keyboards/admin_kb.py | 11 ++- src/keyboards/calendar_kb.py | 72 ++++++++++++---- src/keyboards/client_kb.py | 20 ++++- src/services/admin_service.py | 19 +++-- src/services/client_service.py | 2 +- 9 files changed, 266 insertions(+), 91 deletions(-) rename src/{main.py => __main__.py} (70%) diff --git a/src/__init__.py b/src/__init__.py index 722083b..8c0d369 100644 --- a/src/__init__.py +++ b/src/__init__.py @@ -3,6 +3,4 @@ from . import keyboards from . import services -import main import create_bot - diff --git a/src/main.py b/src/__main__.py similarity index 70% rename from src/main.py rename to src/__main__.py index a9b8f41..8b867db 100644 --- a/src/main.py +++ b/src/__main__.py @@ -6,7 +6,7 @@ async def on_startup(_) -> None: - logger.info("Bot is online!") + logger.info("Bot now is online!") sqlite_db.start_db() @@ -14,7 +14,11 @@ def main(): admin.register_admin_handlers(dp) client.register_client_handlers(dp) shared.register_shared_handlers(dp) - executor.start_polling(dp, skip_updates=True, on_startup=on_startup) + executor.start_polling( + dispatcher=dp, + skip_updates=True, + on_startup=on_startup, + ) if __name__ == '__main__': diff --git a/src/db/sqlite_db.py b/src/db/sqlite_db.py index 3a081da..96874bd 100644 --- a/src/db/sqlite_db.py +++ b/src/db/sqlite_db.py @@ -27,8 +27,10 @@ def start_db() -> None: def sql_get_queue_list(admin_id_: int) -> list: cursor.execute( - "SELECT id, queue_name, start, chat_id, chat_title FROM queues_list WHERE assignee_id = ?", - (admin_id_,) + "SELECT id, queue_name, start, chat_id, chat_title " + "FROM queues_list " + "WHERE assignee_id = ?", + (admin_id_,), ) return cursor.fetchall() @@ -36,8 +38,10 @@ def sql_get_queue_list(admin_id_: int) -> list: async def sql_get_queue_from_list(id_: int) -> tuple: cursor.execute( - "SELECT * FROM queues_list WHERE id = ?", - (id_,) + "SELECT * " + "FROM queues_list " + "WHERE id = ?", + (id_,), ) return cursor.fetchone() @@ -45,7 +49,10 @@ async def sql_get_queue_from_list(id_: int) -> tuple: def sql_get_chat_title(chat_id_: int) -> tuple: cursor.execute( - "SELECT chat_title FROM chat WHERE chat_id = ?", (chat_id_,) + "SELECT chat_title " + "FROM chat " + "WHERE chat_id = ?", + (chat_id_,), ) return cursor.fetchone() @@ -53,7 +60,10 @@ def sql_get_chat_title(chat_id_: int) -> tuple: def sql_get_managed_chats(admin_id_: int) -> list: cursor.execute( - f"SELECT chat_id, chat_title FROM chat WHERE assignee_id = ?", (admin_id_,) + f"SELECT chat_id, chat_title " + f"FROM chat " + f"WHERE assignee_id = ?", + (admin_id_,), ) return cursor.fetchall() @@ -61,41 +71,52 @@ def sql_get_managed_chats(admin_id_: int) -> list: async def sql_add_admin(admin_id_: int, user_name_: str) -> None: cursor.execute( - "INSERT OR IGNORE INTO admin VALUES (?, ?)", - (admin_id_, user_name_) + "INSERT OR IGNORE INTO admin " + "VALUES (?, ?)", + (admin_id_, user_name_), ) conn.commit() async def sql_add_managed_chat(admin_id_: int, chat_id_: int, chat_title_: str) -> None: cursor.execute( - "INSERT INTO chat ('assignee_id', 'chat_id', 'chat_title') VALUES (?, ?, ?)", - (admin_id_, chat_id_, chat_title_) + "INSERT INTO chat " + "('assignee_id', 'chat_id', 'chat_title') " + "VALUES (?, ?, ?)", + (admin_id_, chat_id_, chat_title_), ) conn.commit() async def sql_delete_managed_chat(chat_id_: int) -> None: cursor.execute( - "DELETE FROM chat WHERE chat_id = ?", (chat_id_,) + "DELETE FROM chat " + "WHERE chat_id = ?", + (chat_id_,), ) conn.commit() cursor.execute( - "DELETE FROM queues_list WHERE chat_id = ?", (chat_id_,) + "DELETE FROM queues_list " + "WHERE chat_id = ?", + (chat_id_,), ) conn.commit() async def sql_add_queue(admin_id_: int, queue_name_: str, start_dt: datetime, chat_id_: int, chat_title_: str) -> tuple: cursor.execute( - "INSERT INTO queues_list ('assignee_id', 'queue_name', 'start', 'chat_id', 'chat_title') " - "VALUES (?, ?, ?, ?, ?)", (admin_id_, queue_name_, start_dt, chat_id_, chat_title_) + "INSERT INTO queues_list " + "('assignee_id', 'queue_name', 'start', 'chat_id', 'chat_title') " + "VALUES (?, ?, ?, ?, ?)", + (admin_id_, queue_name_, start_dt, chat_id_, chat_title_), ) conn.commit() cursor.execute( - "SELECT id FROM queues_list WHERE assignee_id = ? AND queue_name = ? AND chat_id = ?", - (admin_id_, queue_name_, chat_id_) + "SELECT id " + "FROM queues_list " + "WHERE assignee_id = ? AND queue_name = ? AND chat_id = ?", + (admin_id_, queue_name_, chat_id_), ) return cursor.fetchone() @@ -103,22 +124,32 @@ async def sql_add_queue(admin_id_: int, queue_name_: str, start_dt: datetime, ch async def sql_delete_queue(id_: int) -> Tuple[int, int]: cursor.execute( - "SELECT chat_id FROM queues_list WHERE id = ?", (id_,) + "SELECT chat_id " + "FROM queues_list " + "WHERE id = ?", + (id_,), ) chat_id: tuple = cursor.fetchone() cursor.execute( - "DELETE FROM queues_list WHERE id = ?", (id_,) + "DELETE FROM queues_list " + "WHERE id = ?", + (id_,), ) conn.commit() cursor.execute( - "SELECT msg_id FROM queue WHERE id = ?", (id_,) + "SELECT msg_id " + "FROM queue " + "WHERE id = ?", + (id_,), ) msg_id: tuple = cursor.fetchone() cursor.execute( - "DELETE FROM queue WHERE id = ?", (id_,) + "DELETE FROM queue " + "WHERE id = ?", + (id_,), ) conn.commit() @@ -127,6 +158,9 @@ async def sql_delete_queue(id_: int) -> Tuple[int, int]: async def sql_post_queue_msg_id(queue_id_: int, msg_id_: int): cursor.execute( - "INSERT INTO queue ('id', 'msg_id') VALUES (?, ?)", (queue_id_, msg_id_) + "INSERT INTO queue " + "('id', 'msg_id') " + "VALUES (?, ?)", + (queue_id_, msg_id_), ) conn.commit() diff --git a/src/handlers/admin.py b/src/handlers/admin.py index 32824bd..bd483a0 100644 --- a/src/handlers/admin.py +++ b/src/handlers/admin.py @@ -1,19 +1,27 @@ from datetime import datetime + from aiogram import types, Dispatcher from aiogram.dispatcher import FSMContext from aiogram.dispatcher.filters import Text from aiogram.dispatcher.filters.state import State, StatesGroup from aiogram.types import InlineKeyboardMarkup +from src.db.sqlite_db import ( + sql_get_queue_list, + sql_add_queue, + sql_add_admin, + sql_delete_queue, + sql_get_managed_chats, + sql_get_chat_title, +) from src.create_bot import dp, bot -from src.db.sqlite_db import sql_get_queue_list, sql_add_queue, sql_add_admin, \ - sql_delete_queue, sql_get_managed_chats, sql_get_chat_title from src.keyboards import admin_kb, calendar_kb from src.keyboards.client_kb import PLAN_QUEUE_TEXT, DELETE_QUEUE_TEXT, PLANNED_QUEUES_TEXT from src.services.admin_service import EarlierException, parse_to_datetime, wait_for_queue_launch class FSMPlanning(StatesGroup): + """Конечный автомат для планирования очередей.""" choose_chat = State() queue_name = State() start_date = State() @@ -21,31 +29,44 @@ class FSMPlanning(StatesGroup): class FSMDeletion(StatesGroup): + """Конечный автомат удаления очереди.""" queue_choice = State() async def cancel_handler(callback: types.CallbackQuery, state: FSMContext) -> None: + """ + Функция-handler отмены действия. + Прекращает последовательность состояний в конечном автомате, попутно удаляя сообщение. + """ await callback.message.delete() await callback.answer('🚫 Действие отменено') await state.finish() async def queues_list_handler(msg: types.Message) -> tuple: + """ + Функция-handler выдачи списка запланированных очередей. + """ found_queues = sql_get_queue_list(msg.from_user.id) if not found_queues: await bot.send_message( msg.from_user.id, "🙊 У вас пока нет запланированных очередей.\nЗапланируем одну?", - reply_markup=admin_kb.inl_plan_kb + reply_markup=admin_kb.inl_plan_kb, ) + return found_queues, None - out_str = str() - for _, queue_name, dt, _, chat_title in found_queues: - out_str += f"📌«{queue_name}» в чате «{chat_title}» " \ - f"{datetime.strptime(dt, '%Y-%m-%d %H:%M:%S%z').strftime('%d.%m.%Y в %H:%M')}\n" + out_str = '\n'.join([ + f"📌«{queue_name}» в чате «{chat_title}» " + f"{datetime.strptime(dt, '%Y-%m-%d %H:%M:%S%z').strftime('%d.%m.%Y в %H:%M')}" + for _, queue_name, dt, _, chat_title in found_queues + ]) - planned_msg = await bot.send_message(msg.from_user.id, f"⤵️ Вот запланированные вами очереди:\n{out_str}") + planned_msg = await bot.send_message( + msg.from_user.id, + f"⤵️ Вот запланированные вами очереди:\n{out_str}", + ) return found_queues, planned_msg @@ -53,33 +74,41 @@ async def queues_list_handler(msg: types.Message) -> tuple: """ Planning queue zone""" -async def start_planning(action) -> None: +async def start_planning(action: types.Message | types.CallbackQuery) -> None: + """ + Функция старта планирования очередей. + Если бот добавлен вами в групповые чаты, он предложит вам + выбрать, в каком именно вы хотите запланировать очередь. + """ await action.answer('📑 Переходим к планированию очереди...') - managed_chats = sql_get_managed_chats(action.from_user.id) if not managed_chats: await bot.send_message( action.from_user.id, "🙊 Вы пока не добавили меня ни в один групповой чат.\n" - "Я могу организовывать очереди только там 💁‍♂️" + "Я могу организовывать очереди только там 💁‍♂️", ) - return await FSMPlanning.choose_chat.set() - await sql_add_admin(action.from_user.id, action.from_user.username) inl_kb_chat_choices = InlineKeyboardMarkup() for chat_id, chat_title in managed_chats: - inl_kb_chat_choices.add(types.InlineKeyboardButton( - text=chat_title, callback_data=f"choose_chat_{chat_id}") + inl_kb_chat_choices.add( + types.InlineKeyboardButton( + text=chat_title, + callback_data=f"choose_chat_{chat_id}", + ) ) inl_kb_chat_choices.add(admin_kb.cancel_button) - await bot.send_message(action.from_user.id, "⤵️Для начала выберите чат, в который вы добавили бота:", - reply_markup=inl_kb_chat_choices) + await bot.send_message( + action.from_user.id, + "⤵️Для начала выберите чат, в который вы добавили бота:", + reply_markup=inl_kb_chat_choices, + ) async def queue_plan_inline_handler(callback: types.CallbackQuery) -> None: @@ -91,10 +120,18 @@ async def queue_plan_handler(msg: types.Message) -> None: async def queue_set_chat_handler(callback: types.CallbackQuery, state: FSMContext) -> None: + """ + Функция-handler сохранения выбранного чата. + Переводит в состояние выбора названия очереди если всё хорошо, + иначе отменяет действие. + """ async with state.proxy() as data: chat_id = int(callback.data[len("choose_chat_"):]) chat_title = sql_get_chat_title(chat_id) if not chat_title: + await callback.answer( + "Кажется, бота уже нет в данном чате, попробуйте снова.", + ) await cancel_handler(callback, state) return @@ -102,46 +139,75 @@ async def queue_set_chat_handler(callback: types.CallbackQuery, state: FSMContex data['chat_title'] = chat_title[0] await FSMPlanning.next() - - await bot.send_message(callback.from_user.id, "📝 Задайте название очереди", - reply_markup=admin_kb.inl_cancel_kb) + await bot.send_message( + callback.from_user.id, + "📝 Задайте название очереди", + reply_markup=admin_kb.inl_cancel_kb, + ) async def set_queue_name_handler(msg: types.Message, state: FSMContext) -> None: + """ + Функция-handler сохранения имени очереди. + Переводит в состояние выбора даты старта очереди, + иначе выводит ошибку и просит повторить ввод. + """ if not msg.text or msg.text in (PLAN_QUEUE_TEXT, DELETE_QUEUE_TEXT, PLANNED_QUEUES_TEXT): await bot.send_message( - msg.from_user.id, '❌ Кажется, вы ничего не написали! Задайте название очереди', - reply_markup=admin_kb.inl_cancel_kb + msg.from_user.id, + '❌ Кажется, вы ничего не написали! Задайте название очереди', + reply_markup=admin_kb.inl_cancel_kb, ) return + async with state.proxy() as data: data['queue_name'] = msg.text + await FSMPlanning.next() await bot.send_message( msg.from_user.id, '📅 Теперь задайте дату запуска очереди через календарь:', - reply_markup=await calendar_kb.Calendar().start_calendar() + reply_markup=await calendar_kb.Calendar().start_calendar(), ) -async def set_date_handler(callback: types.CallbackQuery, callback_data: dict, state: FSMContext) -> None: - selected, date = await calendar_kb.Calendar().process_selection(callback, callback_data) +async def set_date_handler( + callback: types.CallbackQuery, + callback_data: dict, + state: FSMContext, +) -> None: + """ + Функция-handler сохранения выбранной в календаре даты. + Переводит в состояние выбора времени старта очереди. + """ + selected, date = await calendar_kb.Calendar().process_selection( + query=callback, + data=callback_data, + ) if selected: async with state.proxy() as data: data["selected_date"] = date + await FSMPlanning.next() await bot.send_message( callback.from_user.id, '🕓 Теперь задайте время запуска очереди в формате чч:мм (ex. "15:40")', - reply_markup=admin_kb.inl_cancel_kb + reply_markup=admin_kb.inl_cancel_kb, ) async def set_datetime_handler(msg: types.Message, state: FSMContext) -> None: + """ + Функция-handler сохранения времени и других собранных данных в БД. + Выдаёт информационное сообщение о запуске очереди и начинает ожидание. + """ start_datetime: datetime async with state.proxy() as data: try: - start_datetime = parse_to_datetime(data["selected_date"], msg.text) + start_datetime = parse_to_datetime( + data["selected_date"], + msg.text, + ) except ValueError: await bot.send_message( msg.from_user.id, @@ -151,8 +217,9 @@ async def set_datetime_handler(msg: types.Message, state: FSMContext) -> None: return except EarlierException as e: await bot.send_message( - msg.from_user.id, e, - reply_markup=admin_kb.inl_cancel_kb + msg.from_user.id, + text=str(e), + reply_markup=admin_kb.inl_cancel_kb, ) return @@ -160,24 +227,34 @@ async def set_datetime_handler(msg: types.Message, state: FSMContext) -> None: # Добавление собранных данных в бд. queue_name = data['queue_name'] - chat_id, chat_title = data['chat_id'], data['chat_title'] - queue_id = await sql_add_queue(msg.from_user.id, queue_name, start_datetime, chat_id, chat_title) + chat_id = data['chat_id'] + chat_title = data['chat_title'] + queue_id = await sql_add_queue( + msg.from_user.id, + queue_name, + start_datetime, + chat_id, + chat_title, + ) await bot.send_message( msg.from_user.id, f"✅Очередь «{queue_name}» запланирована в чате «{chat_title}»!\n" - f"Начало очереди: {start_datetime.strftime('%d.%m.%Y в %H:%M')}" + f"Начало очереди: {start_datetime.strftime('%d.%m.%Y в %H:%M')}", ) await bot.send_message( chat_id, f"✅Очередь «{queue_name}» запланирована!\n" - f"Начало очереди: {start_datetime.strftime('%d.%m.%Y в %H:%M')}" + f"Начало очереди: {start_datetime.strftime('%d.%m.%Y в %H:%M')}", ) await state.finish() - - await wait_for_queue_launch(start_datetime, chat_id, queue_id[0]) + await wait_for_queue_launch( + start_datetime, + chat_id, + queue_id[0], + ) """ Deleting queue zone""" diff --git a/src/keyboards/admin_kb.py b/src/keyboards/admin_kb.py index e5b6744..1f8b9f8 100644 --- a/src/keyboards/admin_kb.py +++ b/src/keyboards/admin_kb.py @@ -1,8 +1,13 @@ from aiogram.types import InlineKeyboardMarkup, InlineKeyboardButton -cancel_button = InlineKeyboardButton(text='🚫 Отмена', callback_data='cancel_call', ) +cancel_button = InlineKeyboardButton( + text='🚫 Отмена', + callback_data='cancel_call', +) inl_cancel_kb = InlineKeyboardMarkup().add(cancel_button) - inl_plan_kb = InlineKeyboardMarkup().add( - InlineKeyboardButton(text='🗓 Запланировать очередь', callback_data='plan_queue') + InlineKeyboardButton( + text='🗓 Запланировать очередь', + callback_data='plan_queue', + ) ) diff --git a/src/keyboards/calendar_kb.py b/src/keyboards/calendar_kb.py index cdf39ae..049d128 100644 --- a/src/keyboards/calendar_kb.py +++ b/src/keyboards/calendar_kb.py @@ -13,8 +13,11 @@ class Calendar: """Calendar inline keyboard.""" - async def start_calendar(self, year: int = datetime.now(timezone('Europe/Moscow')).year, - month: int = datetime.now(timezone('Europe/Moscow')).month) -> InlineKeyboardMarkup: + async def start_calendar( + self, + year: int = datetime.now(timezone('Europe/Moscow')).year, + month: int = datetime.now(timezone('Europe/Moscow')).month, + ) -> InlineKeyboardMarkup: """ Creates an inline keyboard with the provided year and month :param int year: Year to use in the calendar, if None the current year is used. @@ -56,25 +59,35 @@ async def start_calendar(self, year: int = datetime.now(timezone('Europe/Moscow' dt_now = datetime.now(timezone('Europe/Moscow')) if dt_now > datetime(year, month, day, tzinfo=timezone('Europe/Moscow')) and day != dt_now.day: inline_kb.insert(InlineKeyboardButton( - self.__strike_through(day), callback_data=ignore_callback + self.__strike_through(day), + callback_data=ignore_callback, )) continue inline_kb.insert(InlineKeyboardButton( - str(day), callback_data=calendar_callback.new("DAY", year, month, day) + str(day), + callback_data=calendar_callback.new("DAY", year, month, day), )) # Last row - Buttons. inline_kb.row() inline_kb.insert(InlineKeyboardButton( - "◀️", callback_data=calendar_callback.new("PREV-MONTH", year, month, day) + "◀️", + callback_data=calendar_callback.new("PREV-MONTH", year, month, day), )) - inline_kb.insert(InlineKeyboardButton(" ", callback_data=ignore_callback)) inline_kb.insert(InlineKeyboardButton( - "▶️", callback_data=calendar_callback.new("NEXT-MONTH", year, month, day) + " ", + callback_data=ignore_callback, + )) + inline_kb.insert(InlineKeyboardButton( + "▶️", + callback_data=calendar_callback.new("NEXT-MONTH", year, month, day), )) # For cancelling plan queue. - inline_kb.add(InlineKeyboardButton(text='🚫 Отмена', callback_data='cancel_call')) + inline_kb.add(InlineKeyboardButton( + text='🚫 Отмена', + callback_data='cancel_call'), + ) return inline_kb @@ -95,7 +108,12 @@ async def process_selection(self, query: CallbackQuery, data: dict) -> tuple: and returning the date if so. """ return_data = (False, None) - temp_date = datetime(int(data['year']), int(data['month']), 1, tzinfo=timezone('Europe/Moscow')) + temp_date = datetime( + int(data['year']), + int(data['month']), + day=1, + tzinfo=timezone('Europe/Moscow'), + ) # Processing empty buttons, answering with no action. if data['act'] == "IGNORE": @@ -103,23 +121,47 @@ async def process_selection(self, query: CallbackQuery, data: dict) -> tuple: # User picked a day button, return date. if data['act'] == "DAY": await query.message.delete_reply_markup() # removing inline keyboard - return_data = True, datetime(int(data['year']), int(data['month']), int(data['day']), - tzinfo=timezone('Europe/Moscow')) + return_data = True, datetime( + int(data['year']), + int(data['month']), + int(data['day']), + tzinfo=timezone('Europe/Moscow'), + ) # User navigates to previous year, editing message with new calendar. if data['act'] == "PREV-YEAR": prev_date = temp_date - timedelta(days=365) - await query.message.edit_reply_markup(await self.start_calendar(int(prev_date.year), int(prev_date.month))) + await query.message.edit_reply_markup( + await self.start_calendar( + int(prev_date.year), + int(prev_date.month), + ) + ) # User navigates to next year, editing message with new calendar. if data['act'] == "NEXT-YEAR": next_date = temp_date + timedelta(days=365) - await query.message.edit_reply_markup(await self.start_calendar(int(next_date.year), int(next_date.month))) + await query.message.edit_reply_markup( + await self.start_calendar( + int(next_date.year), + int(next_date.month), + ) + ) # User navigates to previous month, editing message with new calendar. if data['act'] == "PREV-MONTH": prev_date = temp_date - timedelta(days=1) - await query.message.edit_reply_markup(await self.start_calendar(int(prev_date.year), int(prev_date.month))) + await query.message.edit_reply_markup( + await self.start_calendar( + int(prev_date.year), + int(prev_date.month), + ) + ) # User navigates to next month, editing message with new calendar. if data['act'] == "NEXT-MONTH": next_date = temp_date + timedelta(days=31) - await query.message.edit_reply_markup(await self.start_calendar(int(next_date.year), int(next_date.month))) + await query.message.edit_reply_markup( + await self.start_calendar( + int(next_date.year), + int(next_date.month), + ) + ) # At some point user clicks DAY button, returning date. return return_data diff --git a/src/keyboards/client_kb.py b/src/keyboards/client_kb.py index 7d6681a..d697fa7 100644 --- a/src/keyboards/client_kb.py +++ b/src/keyboards/client_kb.py @@ -11,10 +11,22 @@ queue_inl_kb = InlineKeyboardMarkup(row_width=2) queue_inl_kb.row( - InlineKeyboardButton(text='⤴️ Встать в очередь', callback_data='sign_in'), - InlineKeyboardButton(text='↩️ Покинуть очередь', callback_data='sign_out') + InlineKeyboardButton( + text='⤴️ Встать в очередь', + callback_data='sign_in', + ), + InlineKeyboardButton( + text='↩️ Покинуть очередь', + callback_data='sign_out', + ) ) queue_inl_kb.add( - InlineKeyboardButton(text='🔃 Пропустить вперёд', callback_data='skip_ahead'), - InlineKeyboardButton(text='↪️ В хвост очереди', callback_data='in_tail') + InlineKeyboardButton( + text='🔃 Пропустить вперёд', + callback_data='skip_ahead', + ), + InlineKeyboardButton( + text='↪️ В хвост очереди', + callback_data='in_tail', + ) ) diff --git a/src/services/admin_service.py b/src/services/admin_service.py index 3049898..d1d475c 100644 --- a/src/services/admin_service.py +++ b/src/services/admin_service.py @@ -19,16 +19,19 @@ async def wait_for_queue_launch(start_dt: datetime, chat_id: int, queue_id: int) # Check that queue has not been deleted. queue_data = await sql_get_queue_from_list(queue_id) if not queue_data: - await bot.send_message(chat_id, - f"🗑 Кажется, запланированную на это время очередь, удалили :(") + await bot.send_message( + chat_id=chat_id, + text=f"🗑 Кажется, запланированную на это время очередь, удалили :(", + ) return - msg = await bot.send_message(chat_id, - f"🆕 🅠🅤🅔🅤🅔 🆕\n" - f"Очередь «{queue_data[2]}» запущена!\n" - f"", - reply_markup=client_kb.queue_inl_kb - ) + msg = await bot.send_message( + chat_id, + f"🆕 🅠🅤🅔🅤🅔 🆕\n" + f"Очередь «{queue_data[2]}» запущена!\n" + f"", + reply_markup=client_kb.queue_inl_kb + ) try: await msg.pin(disable_notification=False) except BadRequest: diff --git a/src/services/client_service.py b/src/services/client_service.py index 0c6ea40..2200e43 100644 --- a/src/services/client_service.py +++ b/src/services/client_service.py @@ -1,5 +1,5 @@ # Copyright 2022 aaaaaaaalesha - +import asyncio from typing import Tuple STATUS_OK = 0 From 8897ea32164249fd441f9b121432b256a1540b04 Mon Sep 17 00:00:00 2001 From: Aleksey Aleksandrov <55093100+aaaaaaaalesha@users.noreply.github.com> Date: Mon, 24 Apr 2023 21:24:08 +0300 Subject: [PATCH 5/6] some preparing codestyle changes for solving issues --- requirements.txt | Bin 538 -> 536 bytes src/__main__.py | 6 ++ src/db/init_db.sql | 4 +- src/db/sqlite_db.py | 21 +++--- src/handlers/admin.py | 62 +++++++++------- src/handlers/client.py | 126 ++++++++++++++------------------- src/handlers/shared.py | 23 +++--- src/services/admin_service.py | 32 +++++---- src/services/client_service.py | 58 +++++++-------- 9 files changed, 173 insertions(+), 159 deletions(-) diff --git a/requirements.txt b/requirements.txt index c40a00333211c0864f27542ec069266f6d3dfe0f..edfe0ae19f27afd4d6409eed468e7800e6ea1f9b 100644 GIT binary patch delta 12 TcmbQmGJ|Ep5k||6$AcLG9zz6` delta 14 VcmbQiGK*!x5oSXMqm4&{8381n1it_P diff --git a/src/__main__.py b/src/__main__.py index 8b867db..67de275 100644 --- a/src/__main__.py +++ b/src/__main__.py @@ -10,6 +10,11 @@ async def on_startup(_) -> None: sqlite_db.start_db() +async def on_shutdown(_) -> None: + logger.info("Bot now is offline!") + sqlite_db.stop_db() + + def main(): admin.register_admin_handlers(dp) client.register_client_handlers(dp) @@ -18,6 +23,7 @@ def main(): dispatcher=dp, skip_updates=True, on_startup=on_startup, + on_shutdown=on_shutdown, ) diff --git a/src/db/init_db.sql b/src/db/init_db.sql index 8d67776..019258a 100644 --- a/src/db/init_db.sql +++ b/src/db/init_db.sql @@ -29,6 +29,6 @@ CREATE TABLE IF NOT EXISTS queues_list CREATE TABLE IF NOT EXISTS queue ( - id INTEGER REFERENCES queues_list (id), - msg_id INTEGER + id INTEGER REFERENCES queues_list (id), + msg_id INTEGER ); \ No newline at end of file diff --git a/src/db/sqlite_db.py b/src/db/sqlite_db.py index 96874bd..d5d38b1 100644 --- a/src/db/sqlite_db.py +++ b/src/db/sqlite_db.py @@ -5,7 +5,12 @@ from datetime import datetime from typing import Tuple -conn = sqlite3.connect('queue_bot.db') +conn = sqlite3.connect( + os.getenv( + 'DATABASE', + default='queue_bot.db', + ) +) cursor = conn.cursor() logging.basicConfig(level=logging.INFO) @@ -22,7 +27,13 @@ def start_db() -> None: conn.commit() if conn: - logging.info("Data base has been connected!") + logging.info("Database has been connected!") + + +def stop_db() -> None: + cursor.close() + conn.close() + logging.info("Database has been disconnected!") def sql_get_queue_list(admin_id_: int) -> list: @@ -32,7 +43,6 @@ def sql_get_queue_list(admin_id_: int) -> list: "WHERE assignee_id = ?", (admin_id_,), ) - return cursor.fetchall() @@ -43,7 +53,6 @@ async def sql_get_queue_from_list(id_: int) -> tuple: "WHERE id = ?", (id_,), ) - return cursor.fetchone() @@ -54,7 +63,6 @@ def sql_get_chat_title(chat_id_: int) -> tuple: "WHERE chat_id = ?", (chat_id_,), ) - return cursor.fetchone() @@ -65,7 +73,6 @@ def sql_get_managed_chats(admin_id_: int) -> list: f"WHERE assignee_id = ?", (admin_id_,), ) - return cursor.fetchall() @@ -118,7 +125,6 @@ async def sql_add_queue(admin_id_: int, queue_name_: str, start_dt: datetime, ch "WHERE assignee_id = ? AND queue_name = ? AND chat_id = ?", (admin_id_, queue_name_, chat_id_), ) - return cursor.fetchone() @@ -152,7 +158,6 @@ async def sql_delete_queue(id_: int) -> Tuple[int, int]: (id_,), ) conn.commit() - return chat_id[0], msg_id[0] diff --git a/src/handlers/admin.py b/src/handlers/admin.py index bd483a0..76d5c34 100644 --- a/src/handlers/admin.py +++ b/src/handlers/admin.py @@ -14,10 +14,18 @@ sql_get_managed_chats, sql_get_chat_title, ) +from src.keyboards.client_kb import ( + PLAN_QUEUE_TEXT, + DELETE_QUEUE_TEXT, + PLANNED_QUEUES_TEXT, +) +from src.services.admin_service import ( + EarlierException, + parse_to_datetime, + wait_for_queue_launch, +) from src.create_bot import dp, bot from src.keyboards import admin_kb, calendar_kb -from src.keyboards.client_kb import PLAN_QUEUE_TEXT, DELETE_QUEUE_TEXT, PLANNED_QUEUES_TEXT -from src.services.admin_service import EarlierException, parse_to_datetime, wait_for_queue_launch class FSMPlanning(StatesGroup): @@ -74,7 +82,15 @@ async def queues_list_handler(msg: types.Message) -> tuple: """ Planning queue zone""" -async def start_planning(action: types.Message | types.CallbackQuery) -> None: +async def queue_plan_handler(msg: types.Message) -> None: + await __start_planning(msg) + + +async def queue_plan_inline_handler(callback: types.CallbackQuery) -> None: + await __start_planning(callback) + + +async def __start_planning(action: types.Message | types.CallbackQuery) -> None: """ Функция старта планирования очередей. Если бот добавлен вами в групповые чаты, он предложит вам @@ -111,14 +127,6 @@ async def start_planning(action: types.Message | types.CallbackQuery) -> None: ) -async def queue_plan_inline_handler(callback: types.CallbackQuery) -> None: - await start_planning(callback) - - -async def queue_plan_handler(msg: types.Message) -> None: - await start_planning(msg) - - async def queue_set_chat_handler(callback: types.CallbackQuery, state: FSMContext) -> None: """ Функция-handler сохранения выбранного чата. @@ -148,8 +156,7 @@ async def queue_set_chat_handler(callback: types.CallbackQuery, state: FSMContex async def set_queue_name_handler(msg: types.Message, state: FSMContext) -> None: """ - Функция-handler сохранения имени очереди. - Переводит в состояние выбора даты старта очереди, + Функция-handler сохранения имени очереди. Переводит в состояние выбора даты старта очереди, иначе выводит ошибку и просит повторить ввод. """ if not msg.text or msg.text in (PLAN_QUEUE_TEXT, DELETE_QUEUE_TEXT, PLANNED_QUEUES_TEXT): @@ -204,10 +211,7 @@ async def set_datetime_handler(msg: types.Message, state: FSMContext) -> None: start_datetime: datetime async with state.proxy() as data: try: - start_datetime = parse_to_datetime( - data["selected_date"], - msg.text, - ) + start_datetime = parse_to_datetime(data["selected_date"], msg.text) except ValueError: await bot.send_message( msg.from_user.id, @@ -261,6 +265,9 @@ async def set_datetime_handler(msg: types.Message, state: FSMContext) -> None: async def choose_queue_to_delete_handler(msg: types.Message) -> None: + """ + Функция-handler выбора запланированной очереди для удаления. + """ planned_queues, del_msg = await queues_list_handler(msg) if not planned_queues or del_msg is None: @@ -280,20 +287,23 @@ async def choose_queue_to_delete_handler(msg: types.Message) -> None: await FSMDeletion.queue_choice.set() +@dp.callback_query_handler(Text(startswith='delete_queue_'), state=FSMDeletion.queue_choice) async def delete_queue_handler(callback: types.CallbackQuery, state: FSMContext): + """ + Функция-handler удаления запланированной очереди. + """ chat_id, msg_id = await sql_delete_queue(int(callback.data[len("delete_queue_"):])) - await bot.delete_message(chat_id, msg_id) - await callback.answer('💥 Очередь удалена') - await messages_tuple[0].delete() - await messages_tuple[1].delete() - await state.finish() + try: + await bot.delete_message(chat_id, msg_id) + await messages_tuple[0].delete() + await messages_tuple[1].delete() + finally: + await callback.answer('💥 Очередь удалена') + await state.finish() def register_admin_handlers(dp_: Dispatcher) -> None: - """ - Function for registration all handlers for admin. - :return: None - """ + """Регистрация всех handler-функций для админа.""" dp_.register_callback_query_handler( cancel_handler, text="cancel_call", state="*" ) diff --git a/src/handlers/client.py b/src/handlers/client.py index 4f06ddc..43dcc14 100644 --- a/src/handlers/client.py +++ b/src/handlers/client.py @@ -7,26 +7,24 @@ from src.create_bot import dp, bot from src.keyboards.client_kb import main_kb, queue_inl_kb from src.services import client_service +from src.services.client_service import QueueStatus async def start_handler(message: types.Message): - """ - Handler for `/start` command. - """ - await bot.send_message(message.from_user.id, - f"Привет, {message.from_user.first_name} (@{message.from_user.username})!\n" - f"Я IU8-QueueBot - бот для создания очередей.\n" - "Давайте начнём: можете использовать команды (/help) " - f"или кнопки клавиатуры для работы со мной. В случае возникновения проблем, пишите " - f"@aaaaaaaalesha", - reply_markup=main_kb - ) + """Функция-handler для команды `/start`.""" + await bot.send_message( + message.from_user.id, + f"Привет, {message.from_user.first_name} (@{message.from_user.username})!\n" + f"Я IU8-QueueBot - бот для создания очередей.\n" + "Давайте начнём: можете использовать команды (/help) " + f"или кнопки клавиатуры для работы со мной. В случае возникновения проблем, пишите " + f"@aaaaaaaalesha", + reply_markup=main_kb, + ) async def help_handler(message: types.Message): - """ - Handler for `/help` command. - """ + """Функция-handler для команды `/help`.""" await bot.send_message( message.from_user.id, "/start - Начало работы с ботом \n" @@ -45,39 +43,45 @@ async def flood_handler(update: types.Update, exception: RetryAfter): async def sign_in_queue_handler(callback: types.CallbackQuery): user = callback.from_user done, _ = await asyncio.wait( - (client_service.add_queuer_text(callback.message.text, user.first_name, user.username),) + (client_service.add_queuer_text( + callback.message.text, + user.first_name, + user.username + ),) ) for future in done: new_text, status_code = future.result() - if status_code != client_service.STATUS_OK: - if status_code == client_service.STATUS_ALREADY_IN: + match status_code: + case QueueStatus.OK: + await asyncio.wait((callback.message.edit_text( + text=new_text, + reply_markup=queue_inl_kb, + ),)) + case QueueStatus.EXISTS: await callback.answer("❕ Вы уже в очереди.") - return - await asyncio.wait((callback.message.edit_text(text=new_text, reply_markup=queue_inl_kb),)) async def sign_out_queue_handler(callback: types.CallbackQuery): user = callback.from_user done, _ = await asyncio.wait( - (client_service.delete_queuer_text(callback.message.text, user.first_name, user.username),) + (client_service.delete_queuer_text( + callback.message.text, + user.first_name, + user.username, + ),) ) for future in done: new_text, status_code = future.result() - if status_code != client_service.STATUS_OK: - if status_code == client_service.STATUS_NO_QUEUERS: - await callback.answer("❕ В очереди ещё нет участников.") - return - if status_code == client_service.STATUS_NOT_QUEUER: - await callback.answer(f"❕ @{callback.from_user.username} ещё не участник очереди.") - return - await asyncio.wait((callback.message.edit_text(text=new_text, reply_markup=queue_inl_kb),)) + match status_code: + case QueueStatus.OK: + await asyncio.wait((callback.message.edit_text(text=new_text, reply_markup=queue_inl_kb),)) + case QueueStatus.EMPTY | QueueStatus.NOT_QUEUER as answer: + await callback.answer(answer) async def skip_ahead_handler(callback: types.CallbackQuery): - new_text, status_code = str(), -1 - user = callback.from_user done, _ = await asyncio.wait( (client_service.skip_ahead(callback.message.text, user.first_name, user.username),) @@ -85,29 +89,19 @@ async def skip_ahead_handler(callback: types.CallbackQuery): for future in done: new_text, status_code = future.result() - - if status_code != client_service.STATUS_OK: - if status_code == client_service.STATUS_NO_QUEUERS: - await callback.answer("❕ В очереди ещё нет участников.") - return - if status_code == client_service.STATUS_ONE_QUEUER: - await callback.answer("❕ В очереди только один участник.") - return - if status_code == client_service.STATUS_NOT_QUEUER: - await callback.answer("❕ Вы ещё не участник очереди.") - return - if status_code == client_service.STATUS_NO_AFTER: - await callback.answer("❕ Вы крайний в очереди.") - return - await callback.answer("❕ Что-то пошло не так.") - return - - await callback.message.edit_text(text=new_text, reply_markup=queue_inl_kb) + match status_code: + case QueueStatus.OK: + await callback.message.edit_text(text=new_text, reply_markup=queue_inl_kb) + case (QueueStatus.EMPTY + | QueueStatus.ONE_QUEUER + | QueueStatus.NOT_QUEUER + | QueueStatus.NO_AFTER) as answer: + await callback.answer(answer) + case _: + await callback.answer("❕ Что-то пошло не так") async def push_tail_handler(callback: types.CallbackQuery): - new_text, status_code = str(), -1 - user = callback.from_user done, _ = await asyncio.wait( (client_service.push_tail(callback.message.text, user.first_name, user.username),) @@ -115,30 +109,20 @@ async def push_tail_handler(callback: types.CallbackQuery): for future in done: new_text, status_code = future.result() - - if status_code != client_service.STATUS_OK: - if status_code == client_service.STATUS_NO_QUEUERS: - await callback.answer("❕ В очереди ещё нет участников.") - return - if status_code == client_service.STATUS_ONE_QUEUER: - await callback.answer("❕ В очереди только один участник.") - return - if status_code == client_service.STATUS_NOT_QUEUER: - await callback.answer("❕ Вы ещё не участник очереди.") - return - if status_code == client_service.STATUS_NO_AFTER: - await callback.answer("❕ Вы крайний в очереди.") - return - await callback.answer("❕ Что-то пошло не так.") - return - - await callback.message.edit_text(text=new_text, reply_markup=queue_inl_kb) + match status_code: + case QueueStatus.OK: + await callback.message.edit_text(text=new_text, reply_markup=queue_inl_kb) + case (QueueStatus.EMPTY + | QueueStatus.ONE_QUEUER + | QueueStatus.NOT_QUEUER + | QueueStatus.NO_AFTER) as answer: + await callback.answer(answer) + case _: + await callback.answer("❕ Что-то пошло не так") def register_client_handlers(dp_: Dispatcher) -> None: - """ - Function registers all handlers for client. - """ + """Регистрация всех handler-функций для клиента.""" dp_.register_message_handler(start_handler, commands='start', state=None) dp_.register_message_handler(help_handler, commands="help", state=None) dp_.register_errors_handler(flood_handler, exception=RetryAfter) diff --git a/src/handlers/shared.py b/src/handlers/shared.py index 01993ad..199aa07 100644 --- a/src/handlers/shared.py +++ b/src/handlers/shared.py @@ -4,27 +4,32 @@ from src.db.sqlite_db import sql_add_admin, sql_add_managed_chat, sql_delete_managed_chat -async def new_chat_handler(message: types.Message): +async def new_chat_handler(message: types.Message) -> None: + """ + Функция-handler обработки добавления бота в групповой чат. + """ # Check that bot has been added to chat. if any(bot.id == member.id for member in message.new_chat_members): user = message.from_user await sql_add_admin(user.id, user.username) await sql_add_managed_chat(user.id, message.chat.id, message.chat.title) - await message.reply(f"Привет! Теперь {user.first_name} (@{user.username}) – " - "администратор очередей в этом чате.\n" - "Запланировать её можно в личном чате со мной. Приятной работы!") + await message.reply( + f"Привет! Теперь {user.first_name} (@{user.username}) – " + "администратор очередей в этом чате.\n" + "Запланировать её можно в личном чате со мной. Приятной работы!" + ) -async def left_chat_handler(message: types.Message): +async def left_chat_handler(message: types.Message) -> None: + """ + Функция-handler обработки удаления бота из группового чата. + """ # Check that bot has been deleted from chat. if bot.id == message.left_chat_member.id: await sql_delete_managed_chat(message.chat.id) def register_shared_handlers(dp_: Dispatcher) -> None: - """ - Function for registration all handlers for everyone. - """ - # dp.register_message_handler(echo, state=None) + """Регистрация всех публичных handler-функций.""" dp_.register_message_handler(new_chat_handler, content_types=types.ContentTypes.NEW_CHAT_MEMBERS) dp_.register_message_handler(left_chat_handler, content_types=types.ContentTypes.LEFT_CHAT_MEMBER) diff --git a/src/services/admin_service.py b/src/services/admin_service.py index d1d475c..94eb214 100644 --- a/src/services/admin_service.py +++ b/src/services/admin_service.py @@ -1,6 +1,4 @@ -# Copyright 2021 aaaaaaaalesha - -from datetime import datetime, timedelta +from datetime import datetime from pytz import timezone import asyncio from aiogram.utils.exceptions import BadRequest @@ -15,8 +13,12 @@ class EarlierException(Exception): async def wait_for_queue_launch(start_dt: datetime, chat_id: int, queue_id: int) -> None: + """ + Ожидание начала запуска очереди в групповом чате. + """ await asyncio.sleep((start_dt - datetime.now(timezone('Europe/Moscow'))).seconds) - # Check that queue has not been deleted. + + # Проверим, что очередь не была удалена. queue_data = await sql_get_queue_from_list(queue_id) if not queue_data: await bot.send_message( @@ -27,9 +29,7 @@ async def wait_for_queue_launch(start_dt: datetime, chat_id: int, queue_id: int) msg = await bot.send_message( chat_id, - f"🆕 🅠🅤🅔🅤🅔 🆕\n" - f"Очередь «{queue_data[2]}» запущена!\n" - f"", + f"🆕 🅠🅤🅔🅤🅔 🆕\nОчередь «{queue_data[2]}» запущена!\n\n", reply_markup=client_kb.queue_inl_kb ) try: @@ -40,12 +40,16 @@ async def wait_for_queue_launch(start_dt: datetime, chat_id: int, queue_id: int) await sql_post_queue_msg_id(queue_id, msg.message_id) -def parse_to_datetime(date: datetime, text: str) -> datetime: +def parse_to_datetime(date: datetime, input_time: str) -> datetime: + """ + Парсинг времени в формате hh:mm, а также проверка, что введённое время позже указанного. + """ dt_now = datetime.now(timezone('Europe/Moscow')) - h, m = tuple(map(int, text.split(':'))) - - resulted_date = date - if resulted_date.replace(hour=h, minute=m, second=0) < dt_now: - raise EarlierException(f"❌ Введённое время раньше текущего!\nСейчас {dt_now.strftime('%H:%M')}") + h, m = map(int, input_time.split(':')) + resulted_date = date.replace(hour=h, minute=m, tzinfo=timezone('Europe/Moscow')) + if resulted_date < dt_now: + raise EarlierException( + f"❌ Введённое время раньше текущего!\nСейчас {dt_now.strftime('%H:%M')}" + ) - return date.replace(hour=h, minute=m, second=0, tzinfo=dt_now.tzinfo) + return resulted_date diff --git a/src/services/client_service.py b/src/services/client_service.py index 2200e43..e866160 100644 --- a/src/services/client_service.py +++ b/src/services/client_service.py @@ -1,34 +1,34 @@ -# Copyright 2022 aaaaaaaalesha -import asyncio -from typing import Tuple +from enum import StrEnum -STATUS_OK = 0 -STATUS_ALREADY_IN = 1 -STATUS_NO_QUEUERS = 2 -STATUS_ONE_QUEUER = 3 -STATUS_NOT_QUEUER = 4 -STATUS_NO_AFTER = 5 +class QueueStatus(StrEnum): + OK = '👍' + EXISTS = '❕ Вы уже в очереди' + EMPTY = '❕ В очереди ещё нет участников' + ONE_QUEUER = '❕ В очереди только один участник' + NOT_QUEUER = '❕ Вы ещё не участник очереди' + NO_AFTER = '❕ Вы крайний в очереди' -async def add_queuer_text(old_text: str, first_name: str, username: str) -> Tuple[str, int]: + +async def add_queuer_text(old_text: str, first_name: str, username: str) -> tuple[str, QueueStatus]: lines = old_text.split('\n') match_str = f"{first_name} (@{username})" for i in range(2, len(lines)): if lines[i].rfind(match_str) != -1: - return str(), STATUS_ALREADY_IN + return str(), QueueStatus.EXISTS lines.append(f"{len(lines) - 1}. {match_str}") - return '\n'.join(lines), STATUS_OK + return '\n'.join(lines), QueueStatus.OK -async def delete_queuer_text(old_text: str, first_name: str, username: str) -> Tuple[str, int]: +async def delete_queuer_text(old_text: str, first_name: str, username: str) -> tuple[str, QueueStatus]: lines = old_text.split('\n') match_str = f"{first_name} (@{username})" if len(lines) == 2: - return str(), STATUS_NO_QUEUERS + return str(), QueueStatus.EMPTY else: index_changer = -1 for i in range(2, len(lines)): @@ -38,38 +38,38 @@ async def delete_queuer_text(old_text: str, first_name: str, username: str) -> T break if index_changer == -1: - return str(), STATUS_NOT_QUEUER + return str(), QueueStatus.NOT_QUEUER # If queuer is last in queue. if index_changer == len(lines): - return '\n'.join(lines), STATUS_OK + return '\n'.join(lines), QueueStatus.OK for i in range(index_changer, len(lines)): lines[i] = lines[i].replace(f"{i}. ", f"{i - 1}. ", 1) - return '\n'.join(lines), STATUS_OK + return '\n'.join(lines), QueueStatus.OK -async def skip_ahead(old_text: str, first_name: str, username: str) -> Tuple[str, int]: +async def skip_ahead(old_text: str, first_name: str, username: str) -> tuple[str, QueueStatus]: lines = old_text.split('\n') if len(lines) == 2: - return str(), STATUS_NO_QUEUERS + return str(), QueueStatus.EMPTY if len(lines) == 3: - return str(), STATUS_ONE_QUEUER + return str(), QueueStatus.ONE_QUEUER index_changer = -1 match_str = f"{first_name} (@{username})" for i in range(2, len(lines)): if lines[i].rfind(match_str) != -1: if i == len(lines) - 1: - return str(), STATUS_NO_AFTER + return str(), QueueStatus.NO_AFTER index_changer = i break if index_changer == -1: - return str(), STATUS_NOT_QUEUER + return str(), QueueStatus.NOT_QUEUER lines[index_changer] = lines[index_changer].replace(f"{index_changer - 1}. ", f"{index_changer}. ", 1) lines[index_changer + 1] = lines[index_changer + 1].replace(f"{index_changer}. ", f"{index_changer - 1}. ", 1) @@ -77,17 +77,17 @@ async def skip_ahead(old_text: str, first_name: str, username: str) -> Tuple[str # Swap. lines[index_changer], lines[index_changer + 1] = lines[index_changer + 1], lines[index_changer] - return '\n'.join(lines), STATUS_OK + return '\n'.join(lines), QueueStatus.OK -async def push_tail(old_text: str, first_name: str, username: str) -> Tuple[str, int]: +async def push_tail(old_text: str, first_name: str, username: str) -> tuple[str, QueueStatus]: lines = old_text.split('\n') if len(lines) == 2: - return str(), STATUS_NO_QUEUERS + return str(), QueueStatus.EMPTY if len(lines) == 3: - return str(), STATUS_ONE_QUEUER + return str(), QueueStatus.ONE_QUEUER index_changer = -1 del_queuer = str() @@ -96,17 +96,17 @@ async def push_tail(old_text: str, first_name: str, username: str) -> Tuple[str, for i in range(2, len(lines)): if lines[i].rfind(match_str) != -1: if i + 1 == len(lines): - return str(), STATUS_NO_AFTER + return str(), QueueStatus.NO_AFTER del_queuer = lines.pop(i) index_changer = i break if index_changer == -1: - return str(), STATUS_NOT_QUEUER + return str(), QueueStatus.NOT_QUEUER for i in range(index_changer, len(lines)): lines[i] = lines[i].replace(f"{i}. ", f"{i - 1}. ", 1) lines.append(del_queuer.replace(f"{index_changer - 1}. ", f"{len(lines) - 1}. ", 1)) - return '\n'.join(lines), STATUS_OK + return '\n'.join(lines), QueueStatus.OK From e4899df0495112cf61416aeacad1d42ed6330562 Mon Sep 17 00:00:00 2001 From: Aleksey Aleksandrov <55093100+aaaaaaaalesha@users.noreply.github.com> Date: Mon, 24 Apr 2023 22:05:36 +0300 Subject: [PATCH 6/6] some preparing codestyle changes for solving issues --- src/__main__.py | 3 + src/handlers/admin.py | 16 +++-- src/handlers/client.py | 123 +++++++++++++++++---------------- src/services/admin_service.py | 15 ++-- src/services/client_service.py | 1 - 5 files changed, 87 insertions(+), 71 deletions(-) diff --git a/src/__main__.py b/src/__main__.py index 67de275..093c951 100644 --- a/src/__main__.py +++ b/src/__main__.py @@ -16,9 +16,12 @@ async def on_shutdown(_) -> None: def main(): + # Регистрация handler-функций. admin.register_admin_handlers(dp) client.register_client_handlers(dp) shared.register_shared_handlers(dp) + + # Запуск бота в режиме опроса. executor.start_polling( dispatcher=dp, skip_updates=True, diff --git a/src/handlers/admin.py b/src/handlers/admin.py index 76d5c34..7937052 100644 --- a/src/handlers/admin.py +++ b/src/handlers/admin.py @@ -243,13 +243,13 @@ async def set_datetime_handler(msg: types.Message, state: FSMContext) -> None: await bot.send_message( msg.from_user.id, - f"✅Очередь «{queue_name}» запланирована в чате «{chat_title}»!\n" + f"✅ Очередь «{queue_name}» запланирована в чате «{chat_title}»!\n" f"Начало очереди: {start_datetime.strftime('%d.%m.%Y в %H:%M')}", ) await bot.send_message( chat_id, - f"✅Очередь «{queue_name}» запланирована!\n" + f"✅ Очередь «{queue_name}» запланирована!\n" f"Начало очереди: {start_datetime.strftime('%d.%m.%Y в %H:%M')}", ) @@ -281,8 +281,14 @@ async def choose_queue_to_delete_handler(msg: types.Message) -> None: inl_kb_choices.add(admin_kb.cancel_button) global messages_tuple - messages_tuple = (del_msg, await bot.send_message(msg.from_user.id, '🗑 Выберите очередь, которую хотите удалить:', - reply_markup=inl_kb_choices)) + messages_tuple = ( + del_msg, + await bot.send_message( + msg.from_user.id, + '🗑 Выберите очередь, которую хотите удалить:', + reply_markup=inl_kb_choices, + ) + ) await FSMDeletion.queue_choice.set() @@ -297,6 +303,8 @@ async def delete_queue_handler(callback: types.CallbackQuery, state: FSMContext) await bot.delete_message(chat_id, msg_id) await messages_tuple[0].delete() await messages_tuple[1].delete() + except TypeError: + pass finally: await callback.answer('💥 Очередь удалена') await state.finish() diff --git a/src/handlers/client.py b/src/handlers/client.py index 43dcc14..fbf4327 100644 --- a/src/handlers/client.py +++ b/src/handlers/client.py @@ -37,88 +37,91 @@ async def help_handler(message: types.Message): async def flood_handler(update: types.Update, exception: RetryAfter): - await update.message.answer(f"Не так быстро! Подождите {exception.timeout} секунд") + answer_msg = f"Не так быстро! Подождите {exception.timeout} секунд" + if update.message is not None: + await update.message.answer(answer_msg) + elif update.callback_query is not None: + await update.message.answer(answer_msg) async def sign_in_queue_handler(callback: types.CallbackQuery): user = callback.from_user - done, _ = await asyncio.wait( - (client_service.add_queuer_text( - callback.message.text, - user.first_name, - user.username - ),) + new_text, status_code = await client_service.add_queuer_text( + callback.message.text, + user.first_name, + user.username, ) - for future in done: - new_text, status_code = future.result() - match status_code: - case QueueStatus.OK: - await asyncio.wait((callback.message.edit_text( - text=new_text, - reply_markup=queue_inl_kb, - ),)) - case QueueStatus.EXISTS: - await callback.answer("❕ Вы уже в очереди.") + match status_code: + case QueueStatus.OK: + await callback.message.edit_text( + text=new_text, + reply_markup=queue_inl_kb, + ) + case QueueStatus.EXISTS: + await callback.answer("❕ Вы уже в очереди.") async def sign_out_queue_handler(callback: types.CallbackQuery): user = callback.from_user - done, _ = await asyncio.wait( - (client_service.delete_queuer_text( - callback.message.text, - user.first_name, - user.username, - ),) + new_text, status_code = await client_service.delete_queuer_text( + callback.message.text, + user.first_name, + user.username, ) - for future in done: - new_text, status_code = future.result() - - match status_code: - case QueueStatus.OK: - await asyncio.wait((callback.message.edit_text(text=new_text, reply_markup=queue_inl_kb),)) - case QueueStatus.EMPTY | QueueStatus.NOT_QUEUER as answer: - await callback.answer(answer) + match status_code: + case QueueStatus.OK: + await callback.message.edit_text( + text=new_text, + reply_markup=queue_inl_kb, + ) + case QueueStatus.EMPTY | QueueStatus.NOT_QUEUER as answer: + await callback.answer(answer) async def skip_ahead_handler(callback: types.CallbackQuery): user = callback.from_user - done, _ = await asyncio.wait( - (client_service.skip_ahead(callback.message.text, user.first_name, user.username),) + new_text, status_code = await client_service.skip_ahead( + callback.message.text, + user.first_name, + user.username, ) - - for future in done: - new_text, status_code = future.result() - match status_code: - case QueueStatus.OK: - await callback.message.edit_text(text=new_text, reply_markup=queue_inl_kb) - case (QueueStatus.EMPTY - | QueueStatus.ONE_QUEUER - | QueueStatus.NOT_QUEUER - | QueueStatus.NO_AFTER) as answer: - await callback.answer(answer) - case _: - await callback.answer("❕ Что-то пошло не так") + match status_code: + case QueueStatus.OK: + await callback.message.edit_text( + text=new_text, + reply_markup=queue_inl_kb, + ) + case (QueueStatus.EMPTY + | QueueStatus.ONE_QUEUER + | QueueStatus.NOT_QUEUER + | QueueStatus.NO_AFTER) as answer: + await callback.answer(answer) + case _: + await callback.answer("❕ Что-то пошло не так") async def push_tail_handler(callback: types.CallbackQuery): user = callback.from_user - done, _ = await asyncio.wait( - (client_service.push_tail(callback.message.text, user.first_name, user.username),) + new_text, status_code = await client_service.push_tail( + callback.message.text, + user.first_name, + user.username, ) - for future in done: - new_text, status_code = future.result() - match status_code: - case QueueStatus.OK: - await callback.message.edit_text(text=new_text, reply_markup=queue_inl_kb) - case (QueueStatus.EMPTY - | QueueStatus.ONE_QUEUER - | QueueStatus.NOT_QUEUER - | QueueStatus.NO_AFTER) as answer: - await callback.answer(answer) - case _: - await callback.answer("❕ Что-то пошло не так") + match status_code: + case QueueStatus.OK: + await callback.message.edit_text( + text=new_text, + reply_markup=queue_inl_kb, + ) + case (QueueStatus.EMPTY + | QueueStatus.ONE_QUEUER + | QueueStatus.NOT_QUEUER + | QueueStatus.NO_AFTER) as answer: + await callback.answer(answer) + case _: + await callback.answer("❕ Что-то пошло не так") def register_client_handlers(dp_: Dispatcher) -> None: diff --git a/src/services/admin_service.py b/src/services/admin_service.py index 94eb214..729aa35 100644 --- a/src/services/admin_service.py +++ b/src/services/admin_service.py @@ -16,7 +16,9 @@ async def wait_for_queue_launch(start_dt: datetime, chat_id: int, queue_id: int) """ Ожидание начала запуска очереди в групповом чате. """ - await asyncio.sleep((start_dt - datetime.now(timezone('Europe/Moscow'))).seconds) + await asyncio.sleep( + (start_dt - datetime.now(timezone('Europe/Moscow'))).seconds + ) # Проверим, что очередь не была удалена. queue_data = await sql_get_queue_from_list(queue_id) @@ -29,7 +31,7 @@ async def wait_for_queue_launch(start_dt: datetime, chat_id: int, queue_id: int) msg = await bot.send_message( chat_id, - f"🆕 🅠🅤🅔🅤🅔 🆕\nОчередь «{queue_data[2]}» запущена!\n\n", + f"🆕 🅠🅤🅔🅤🅔 🆕\n Очередь «{queue_data[2]}» запущена!\n\n", reply_markup=client_kb.queue_inl_kb ) try: @@ -45,11 +47,12 @@ def parse_to_datetime(date: datetime, input_time: str) -> datetime: Парсинг времени в формате hh:mm, а также проверка, что введённое время позже указанного. """ dt_now = datetime.now(timezone('Europe/Moscow')) - h, m = map(int, input_time.split(':')) - resulted_date = date.replace(hour=h, minute=m, tzinfo=timezone('Europe/Moscow')) - if resulted_date < dt_now: + h, m = tuple(map(int, input_time.split(':'))) + + resulted_date = date + if resulted_date.replace(hour=h, minute=m, second=0) < dt_now: raise EarlierException( f"❌ Введённое время раньше текущего!\nСейчас {dt_now.strftime('%H:%M')}" ) - return resulted_date + return date.replace(hour=h, minute=m, second=0, tzinfo=dt_now.tzinfo) diff --git a/src/services/client_service.py b/src/services/client_service.py index e866160..9c66c22 100644 --- a/src/services/client_service.py +++ b/src/services/client_service.py @@ -12,7 +12,6 @@ class QueueStatus(StrEnum): async def add_queuer_text(old_text: str, first_name: str, username: str) -> tuple[str, QueueStatus]: lines = old_text.split('\n') - match_str = f"{first_name} (@{username})" for i in range(2, len(lines)): if lines[i].rfind(match_str) != -1: