From a1f6b2ae4709cca11a62558b2d1eb4a4b030b312 Mon Sep 17 00:00:00 2001 From: Olly Hearn Date: Fri, 9 Jun 2023 18:21:36 +0300 Subject: [PATCH] upd --- bot/app/bot.py | 293 +++++++++++++++++++++++++++++-------------- bot/app/db/models.py | 13 +- bot/app/keyboards.py | 34 ++++- bot/app/textbook.py | 18 ++- 4 files changed, 254 insertions(+), 104 deletions(-) diff --git a/bot/app/bot.py b/bot/app/bot.py index eb412fe..9a12f10 100644 --- a/bot/app/bot.py +++ b/bot/app/bot.py @@ -37,10 +37,14 @@ class States(StatesGroup): changing_queue_name = State() +# Utils + + def get_queue_stats_text(queue: Queue) -> str: s = textbook.queue_stats.format(name=queue.name, count=len(queue.users)) return s + async def get_queue_from_state_data(call: types.CallbackQuery) -> Queue: async with bot.retrieve_data( user_id=call.from_user.id, chat_id=call.message.chat.id @@ -60,6 +64,36 @@ async def get_queue_from_state_data(call: types.CallbackQuery) -> Queue: return queue +def get_first_queue_user(queue: Queue) -> QueueUser: + arr = sorted(queue.users, key=lambda qu: qu.position) + return arr[0] + + +def proceed_queue(queue: Queue) -> bool: + if len(queue.users): + for qu in queue.users: + setattr(qu, "position", qu.position - 1) + first_user = get_first_queue_user(queue) + session.delete(first_user) + session.commit() + return True + return False + + +async def update_queue_users_message(msg: Message, queue: Queue): + users_str = "\n".join([f"{qu.position}. {qu.user.name}" for qu in queue.users]) + await bot.edit_message_text( + chat_id=msg.chat.id, + message_id=msg.id, + text=textbook.queue_users_list.format(name=queue.name, users_str=users_str), + reply_markup=keyboards.queue_users(queue.id), + parse_mode="html", + ) + + +# Basic + + @bot.message_handler(commands=["start"]) async def start(msg: Message): if msg.chat.type == "private": @@ -78,29 +112,47 @@ async def start(msg: Message): if len(msg.text.split()) > 1: command, queue_id = msg.text.split() if queue := session.query(Queue).filter_by(id=queue_id).first(): - last_user = session.query(QueueUser).filter_by(queue_id=queue.id).order_by(QueueUser.position).first() - if last_user: - position = last_user.position + 1 + if ( + not session.query(QueueUser) + .filter_by(queue_id=queue.id, user_id=msg.from_user.id) + .first() + ): + last_user = ( + session.query(QueueUser) + .filter_by(queue_id=queue.id) + .order_by(QueueUser.position.desc()) + .first() + ) + if last_user: + position = last_user.position + 1 + else: + position = 0 + queue_user = QueueUser( + user_id=msg.from_user.id, queue_id=queue.id, position=position + ) + session.add(queue_user) + session.commit() + await bot.send_message( + chat_id=msg.chat.id, + text=textbook.joined_queue.format( + name=queue.name, position=queue_user.position + ), + parse_mode="html", + ) else: - position = 0 - queue_user = QueueUser( - user_id=msg.from_user.id, - queue_id=queue.id, - position=position - ) - session.add(queue_user) - session.commit() - await bot.send_message( - chat_id=msg.chat.id, - text=textbook.joined_queue.format(name=queue.name, position=queue_user.position), - parse_mode='html', - ) + await bot.send_message( + chat_id=msg.chat.id, + text=textbook.error_joining_queue.format(name=queue.name), + parse_mode="html", + ) else: await bot.send_message( chat_id=msg.chat.id, text=textbook.menu.format(name=user.name), reply_markup=keyboards.menu(), ) + else: + await bot.send_message(chat_id=msg.chat.id, text=textbook.groups_plug) @bot.callback_query_handler(func=lambda c: c.data == "to_menu") @@ -115,6 +167,9 @@ async def to_menu_handler(call: types.CallbackQuery): ) +# Main menu + + @bot.callback_query_handler(func=lambda c: c.data == "new_queue") async def new_queue_handler(call: types.CallbackQuery): user = session.query(User).filter_by(id=call.from_user.id).first() @@ -148,6 +203,30 @@ async def my_queues_handler(call: types.CallbackQuery): await bot.answer_callback_query(callback_query_id=call.id) +@bot.callback_query_handler(func=lambda c: c.data == "settings") +async def settings(call: types.CallbackQuery): + await bot.set_state(user_id=call.from_user.id, state=States.default) + await bot.edit_message_text( + chat_id=call.message.chat.id, + message_id=call.message.id, + text=textbook.settings, + reply_markup=keyboards.settings(), + ) + await bot.answer_callback_query(callback_query_id=call.id) + + +@bot.callback_query_handler(func=lambda c: c.data == "about") +async def about_handler(call: types.CallbackQuery): + await bot.answer_callback_query( + callback_query_id=call.id, + text=textbook.about, + show_alert=True, + ) + + +# Queue list menu + + @bot.callback_query_handler(func=lambda c: c.data[:2] == "q:") async def queue_handler(call: types.CallbackQuery, queue_id: str = None): queue_id = call.data[2:] if not queue_id else queue_id @@ -169,6 +248,87 @@ async def queue_handler(call: types.CallbackQuery, queue_id: str = None): await bot.answer_callback_query(callback_query_id=call.id) +# Queue menu + + +@bot.callback_query_handler(func=lambda c: c.data == "get_queue_users") +async def get_queue_users_handler(call: types.CallbackQuery): + if queue := await get_queue_from_state_data(call): + users_str = "\n".join([f"{qu.position}. {qu.user.name}" for qu in queue.users]) + await bot.edit_message_text( + chat_id=call.message.chat.id, + message_id=call.message.id, + text=textbook.queue_users_list.format(name=queue.name, users_str=users_str), + reply_markup=keyboards.queue_users(queue.id), + parse_mode="html", + ) + await bot.answer_callback_query(callback_query_id=call.id) + + +@bot.callback_query_handler(func=lambda c: c.data == "get_queue_link") +async def get_queue_link_handler(call: types.CallbackQuery): + if queue := await get_queue_from_state_data(call): + await bot.send_message( + chat_id=call.message.chat.id, + text=textbook.link_template.format(link=queue.id), + ) + await bot.answer_callback_query(callback_query_id=call.id) + + +@bot.callback_query_handler(func=lambda c: c.data == "queue_settings") +async def queue_settings_handler(call: types.CallbackQuery): + if queue := await get_queue_from_state_data(call): + await bot.edit_message_text( + chat_id=call.message.chat.id, + message_id=call.message.id, + text=textbook.queue_settings.format(name=queue.name), + reply_markup=keyboards.queue_settings(queue_id=queue.id), + ) + await bot.answer_callback_query(callback_query_id=call.id) + + +@bot.callback_query_handler(func=lambda c: c.data == "start_queue") +async def start_queue_handler(call: types.CallbackQuery): + pass + + +# Queue users +@bot.callback_query_handler(func=lambda c: c.data == "kick_first") +async def get_queue_users_handler(call: types.CallbackQuery): + if queue := await get_queue_from_state_data(call): + if queue.owner_id == call.from_user.id: + if proceed_queue(queue): + await bot.answer_callback_query( + callback_query_id=call.id, text=textbook.first_kicked + ) + await update_queue_users_message(call.message, queue) + else: + await bot.answer_callback_query( + callback_query_id=call.id, + text=textbook.kick_first_error, + show_alert=True, + ) + else: + await bot.answer_callback_query( + callback_query_id=call.id, + text=textbook.queue_operational_error, + show_alert=True, + ) + await bot.answer_callback_query(callback_query_id=call.id) + + +@bot.callback_query_handler(func=lambda c: c.data == "swap_users") +async def swap_users_position(call: types.CallbackQuery): + await bot.answer_callback_query( + callback_query_id=call.id, + text=textbook.in_development_plug, + show_alert=True, + ) + + +# Queue settings + + @bot.callback_query_handler(func=lambda c: c.data == "edit_queue_name") async def edit_queue_name_handler(call: types.CallbackQuery): if await get_queue_from_state_data(call): @@ -185,8 +345,7 @@ async def edit_queue_name_handler(call: types.CallbackQuery): func=lambda c: c.data == "cancel", state=States.changing_queue_name ) async def edit_queue_name_cancel_handler(call: types.CallbackQuery): - if queue := get_queue_from_state_data(call): - await queue_handler(call, queue.id) + await queue_settings_handler(call) @bot.message_handler(content_types=["text"], state=States.changing_queue_name) @@ -214,53 +373,33 @@ async def update_queue_name(msg: Message): parse_mode="html", ) -@bot.callback_query_handler(func=lambda c: c.data == "get_queue_users") -async def get_queue_users_handler(call: types.CallbackQuery): - if queue := await get_queue_from_state_data(call): - queue_users = session.query(QueueUser).filter_by(queue_id=queue.id).order_by(QueueUser.position).all() - users_str = "\n".join([f"{qu.position}. {qu.user.name}" for qu in queue_users]) - await bot.edit_message_text( - chat_id=call.message.chat.id, - message_id=call.message.id, - text=textbook.queue_users_list.format(name=queue.name, users_str=users_str), - reply_markup=keyboards.queue_users(queue.id), - parse_mode='html', - ) - await bot.answer_callback_query(callback_query_id=call.id) - -@bot.callback_query_handler(func=lambda c: c.data == "get_queue_link") -async def get_queue_link_handler(call: types.CallbackQuery): - if queue := await get_queue_from_state_data(call): - await bot.send_message( - chat_id=call.message.chat.id, - text=textbook.link_template.format(link=queue.id) - ) +@bot.callback_query_handler(func=lambda c: c.data == "delete_queue_approve") +async def delete_queue_approve_handler(call: types.CallbackQuery): + await bot.edit_message_text( + chat_id=call.message.chat.id, + message_id=call.message.id, + text=textbook.delete_queue_approve, + reply_markup=keyboards.approve_queue_delete(), + ) await bot.answer_callback_query(callback_query_id=call.id) @bot.callback_query_handler(func=lambda c: c.data == "delete_queue") async def delete_queue_handler(call: types.CallbackQuery): if queue := await get_queue_from_state_data(call): - await bot.edit_message_text( - chat_id=call.message.chat.id, - message_id=call.message.id, - text=textbook.queue_users_list.format(name=queue.name, users_str=users_str), - reply_markup=keyboards.queue_users(queue.id), - parse_mode='html', + for qu in queue.users: + session.delete(qu) # TODO: Use SQLAlchemy to cascade-delete all users + session.delete(queue) + session.commit() + await bot.answer_callback_query( + callback_query_id=call.id, text=textbook.queue_deleted ) + await my_queues_handler(call) await bot.answer_callback_query(callback_query_id=call.id) -@bot.callback_query_handler(func=lambda c: c.data == "settings") -async def settings(call: types.CallbackQuery): - await bot.set_state(user_id=call.from_user.id, state=States.default) - await bot.edit_message_text( - chat_id=call.message.chat.id, - message_id=call.message.id, - text=textbook.settings, - reply_markup=keyboards.settings(), - ) +# User settings @bot.callback_query_handler(func=lambda c: c.data == "edit_name") @@ -282,7 +421,7 @@ async def edit_name_cancel_handler(call: types.CallbackQuery): @bot.message_handler(content_types=["text"], state=States.changing_name) -async def update_name(msg: Message): +async def update_queue_name(msg: Message): if len(msg.text) > 40 or "\n" in msg.text: await bot.send_message(chat_id=msg.chat.id, text=textbook.edit_name_error) return None @@ -291,37 +430,15 @@ async def update_name(msg: Message): session.commit() await bot.send_message(chat_id=msg.chat.id, text=textbook.edit_name_success) await asyncio.sleep(1) - await start(msg) + await bot.set_state(user_id=msg.from_user.id, state=States.default) + await bot.send_message( + chat_id=msg.chat.id, + text=textbook.settings, + reply_markup=keyboards.settings(), + ) -@bot.message_handler(commands=["take_part"]) -async def tp(msg: Message): - try: - command, queue_id = msg.text.split() - except: - await bot.send_message(chat_id=msg.chat.id, text="Вы забыли указать очередь") - return - qu = QueueUser(user_id=msg.from_user.id, queue_id=queue_id) - session.add(qu) - session.commit() - await bot.send_message(chat_id=msg.chat.id, text="Вы приняли участие в очереди!") - - -@bot.message_handler(commands=["queue"]) -async def q(msg: Message): - try: - command, queue_id = msg.text.split() - except: - await bot.send_message(chat_id=msg.chat.id, text="Вы забыли указать очередь") - return - queue = session.query(Queue).filter_by(id=queue_id).first() - if queue: - users = [q.user.name for q in queue.users] - await bot.send_message( - chat_id=msg.chat.id, text=f"Пользователи этой очереди: {', '.join(users)}" - ) - else: - await bot.send_message(chat_id=msg.chat.id, text="Очередь не найдена!") +# Other @bot.message_handler(commands=["mystate"]) @@ -330,13 +447,7 @@ async def mystate(msg): await bot.send_message(chat_id=msg.from_user.id, text=state) -@bot.callback_query_handler(func=lambda c: c.data == "about") -async def about_handler(call: types.CallbackQuery): - await bot.answer_callback_query( - callback_query_id=call.id, - text=textbook.about, - show_alert=True, - ) +# Launch async def main(): diff --git a/bot/app/db/models.py b/bot/app/db/models.py index e365a76..e248d2e 100644 --- a/bot/app/db/models.py +++ b/bot/app/db/models.py @@ -1,7 +1,7 @@ from sqlalchemy.ext.declarative import declarative_base from sqlalchemy import Column, Integer, String, BigInteger, ForeignKey from sqlalchemy.dialects.postgresql import UUID -from sqlalchemy.orm import relationship +from sqlalchemy.orm import relationship, backref import uuid @@ -12,8 +12,8 @@ class User(Base): __tablename__ = "user" id = Column(BigInteger, primary_key=True) - name = Column(String) - username = Column(String) + name = Column(String(64)) + username = Column(String(32)) owns_queues = relationship("Queue", backref="owner") takes_part_in_queues = relationship("QueueUser", backref="user") @@ -23,10 +23,13 @@ class Queue(Base): __tablename__ = "queue" id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) - name = Column(String, default="Новая очередь") + name = Column(String(40), default="Новая очередь") + description = Column(String(120), default=None) owner_id = Column(BigInteger, ForeignKey("user.id")) - users = relationship("QueueUser", backref="queue") + users = relationship( + "QueueUser", backref="queue" + ) # TODO: Delete all QueueUser user if Queue deletes class QueueUser(Base): diff --git a/bot/app/keyboards.py b/bot/app/keyboards.py index 4ca4a01..cb705bb 100644 --- a/bot/app/keyboards.py +++ b/bot/app/keyboards.py @@ -26,10 +26,34 @@ def queue_menu() -> keyboard: return keyboard( keyboard=[ [button(text="🔗 Ссылка для вступления", callback_data="get_queue_link")], - [button(text="✏️ Изменить название", callback_data="edit_queue_name")], + [button(text="▶️ Начать очередь", callback_data="start_queue")], [button(text="🫂 Список участников", callback_data="get_queue_users")], - [button(text="❌ Удалить очередь", callback_data="delete_queue")], - [button(text="⬅️ В меню", callback_data="to_menu")], + [button(text="⚙️ Настройки очереди", callback_data="queue_settings")], + [button(text="⬅️ Назад", callback_data="my_queues")], + ] + ) + + +def queue_settings(queue_id: str) -> keyboard: + return keyboard( + keyboard=[ + [button(text="✏️ Изменить название", callback_data="edit_queue_name")], + [ + button( + text="📄 Изменить описание", callback_data="edit_queue_description" + ) + ], + [button(text="❌ Удалить очередь", callback_data="delete_queue_approve")], + [button(text="⬅️ Назад", callback_data=f"q:{queue_id}")], + ] + ) + + +def approve_queue_delete() -> keyboard: + return keyboard( + keyboard=[ + [button(text="✅ Да, удалить очередь", callback_data="delete_queue")], + [button(text="⬅️ Назад", callback_data="queue_settings")], ] ) @@ -50,10 +74,12 @@ def edit_name() -> keyboard: ] ) + def queue_users(queue_id: str) -> keyboard: return keyboard( keyboard=[ - [button(text="🔃 Поменять позиции", callback_data="change_positions")], + [button(text="🔃 Поменять позиции", callback_data="swap_users")], + [button(text="⏩ Кикнуть первого", callback_data="kick_first")], [button(text="⬅️ Назад", callback_data=f"q:{queue_id}")], ] ) diff --git a/bot/app/textbook.py b/bot/app/textbook.py index a3111d7..db1add9 100644 --- a/bot/app/textbook.py +++ b/bot/app/textbook.py @@ -2,13 +2,13 @@ start = "Привет! Я помогу тебе вести очередност start_group = "Привет, я QUEUEBOT 2.0, помогаю создавать очереди в твоих групповых чатах!\n\nЧтобы пользоваться мной в этом чате, пользователь с правами админа должен настроить меня, комманда /settings, также не забудь выдать мне права администратора, чтобы я мог видеть список участников этой группы!" menu = "Привет, {name}! Ты в главном меню" new_queue_created = ( - 'Создана новая очередь: {id}. Редактировать ее можно в меню "➕ Новая очередь"' + "Создана новая очередь: {id}\n\nЗаходи в меню очередей и отправляй приглашения!" ) queue_limit = "Ты достиг лимита очередей (4). Удали свои очереди, или воспользуйся другим аккаунтом!" my_queues_list = "У тебя {count} очередь/и/ей" queue_stats = "Название: {name}\nКоличество участников: {count}" error = "Произошла непредвиденная ошибка!" -queue_operational_error = "Произошла ошибка! Либо вы не являетесь владельцем очереди, либо данные устарели и вам следует заново выбрать очередь в меню!" +queue_operational_error = "Произошла ошибка! Либо ты не владельц очереди, либо данные устарели и вам следует заново выбрать очередь в меню!" edit_queue_name = "Введи новое имя очереди, имя должно быть не длинее 40 символов и не должно содержать переносов строки" settings = "🛠 Меню настроек" edit_name = 'Ты можешь поменять свое имя, которое будет отображаться в очередях. По умолчанию используется твое имя в Телеграме. Имя должно быть не длинее 40 символов и не иметь переносов строки. Пришли мне новое имя, или нажми кнопку "❌ Отмена"' @@ -17,8 +17,18 @@ edit_queue_name_success = "Имя очереди изменено!" edit_name_error = ( 'Новое имя не подходит под условия. Напиши подходящее, или нажми кнопку "❌ Отмена"' ) +queue_settings = "🛠 Настройки очереди {name}" +delete_queue_approve = "Ты собираешься удалить очередь, все участники будут распущены. Подтвердить удаление?" +queue_deleted = "Очередь удалена" queue_users_list = "В очереди {name} следующие участники:\n\n{users_str}" -link_template = "https://t.me/q_ue_bot?start={link}" +link_template = "https://t.me/queue_senko_bot?start={link}" joined_queue = "Ты присоединился к очереди {name}\nТвоя позиция: {position}\n\nКогда придет твоя очередь, я сообщу" +error_joining_queue = "Ты не можешь присоединиться к очереди {name}, так как ты уже в ней состоишь!" +first_kicked = "Первый пользователь кикнут" +kick_first_error = ( + "Действие не выполнено, возможно вы уже вышли из очереди, или очередь пуста?" +) -about = "Бот для очередей.\n\nРазработчик - ollyhearn.\nЯ всегда открыт для вопросов и предложений: @OllyHearn\n\nv0.1.1" +about = "Бот для очередей.\n\nРазработчик - ollyhearn.\nЯ всегда открыт для вопросов и предложений: @OllyHearn\n\nv0.1.1-beta" +groups_plug = "Всем привет, я бот для очередей! В настоящее время идет активная разработка, так что я пока не могу полностью функционировать в группах, но вы всегда можете запустить меня в личном диалоге, создать очередь, и отправить ссылку на очередь сюда. Функционал будет доработан, а пока пользуйтесь мной в личке:\n\nhttps://t.me/queue_senko_bot" +in_development_plug = "Функция в разработке ¯\_(ツ)_/¯"