This commit is contained in:
2023-06-09 18:21:36 +03:00
parent 511d9caa9c
commit a1f6b2ae47
4 changed files with 254 additions and 104 deletions

View File

@ -37,10 +37,14 @@ class States(StatesGroup):
changing_queue_name = State() changing_queue_name = State()
# Utils
def get_queue_stats_text(queue: Queue) -> str: def get_queue_stats_text(queue: Queue) -> str:
s = textbook.queue_stats.format(name=queue.name, count=len(queue.users)) s = textbook.queue_stats.format(name=queue.name, count=len(queue.users))
return s return s
async def get_queue_from_state_data(call: types.CallbackQuery) -> Queue: async def get_queue_from_state_data(call: types.CallbackQuery) -> Queue:
async with bot.retrieve_data( async with bot.retrieve_data(
user_id=call.from_user.id, chat_id=call.message.chat.id 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 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"]) @bot.message_handler(commands=["start"])
async def start(msg: Message): async def start(msg: Message):
if msg.chat.type == "private": if msg.chat.type == "private":
@ -78,22 +112,38 @@ async def start(msg: Message):
if len(msg.text.split()) > 1: if len(msg.text.split()) > 1:
command, queue_id = msg.text.split() command, queue_id = msg.text.split()
if queue := session.query(Queue).filter_by(id=queue_id).first(): 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 (
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: if last_user:
position = last_user.position + 1 position = last_user.position + 1
else: else:
position = 0 position = 0
queue_user = QueueUser( queue_user = QueueUser(
user_id=msg.from_user.id, user_id=msg.from_user.id, queue_id=queue.id, position=position
queue_id=queue.id,
position=position
) )
session.add(queue_user) session.add(queue_user)
session.commit() session.commit()
await bot.send_message( await bot.send_message(
chat_id=msg.chat.id, chat_id=msg.chat.id,
text=textbook.joined_queue.format(name=queue.name, position=queue_user.position), text=textbook.joined_queue.format(
parse_mode='html', name=queue.name, position=queue_user.position
),
parse_mode="html",
)
else:
await bot.send_message(
chat_id=msg.chat.id,
text=textbook.error_joining_queue.format(name=queue.name),
parse_mode="html",
) )
else: else:
await bot.send_message( await bot.send_message(
@ -101,6 +151,8 @@ async def start(msg: Message):
text=textbook.menu.format(name=user.name), text=textbook.menu.format(name=user.name),
reply_markup=keyboards.menu(), 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") @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") @bot.callback_query_handler(func=lambda c: c.data == "new_queue")
async def new_queue_handler(call: types.CallbackQuery): async def new_queue_handler(call: types.CallbackQuery):
user = session.query(User).filter_by(id=call.from_user.id).first() 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) 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:") @bot.callback_query_handler(func=lambda c: c.data[:2] == "q:")
async def queue_handler(call: types.CallbackQuery, queue_id: str = None): async def queue_handler(call: types.CallbackQuery, queue_id: str = None):
queue_id = call.data[2:] if not queue_id else queue_id 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) 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") @bot.callback_query_handler(func=lambda c: c.data == "edit_queue_name")
async def edit_queue_name_handler(call: types.CallbackQuery): async def edit_queue_name_handler(call: types.CallbackQuery):
if await get_queue_from_state_data(call): 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 func=lambda c: c.data == "cancel", state=States.changing_queue_name
) )
async def edit_queue_name_cancel_handler(call: types.CallbackQuery): async def edit_queue_name_cancel_handler(call: types.CallbackQuery):
if queue := get_queue_from_state_data(call): await queue_settings_handler(call)
await queue_handler(call, queue.id)
@bot.message_handler(content_types=["text"], state=States.changing_queue_name) @bot.message_handler(content_types=["text"], state=States.changing_queue_name)
@ -214,27 +373,14 @@ async def update_queue_name(msg: Message):
parse_mode="html", parse_mode="html",
) )
@bot.callback_query_handler(func=lambda c: c.data == "get_queue_users")
async def get_queue_users_handler(call: types.CallbackQuery): @bot.callback_query_handler(func=lambda c: c.data == "delete_queue_approve")
if queue := await get_queue_from_state_data(call): async def delete_queue_approve_handler(call: types.CallbackQuery):
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( await bot.edit_message_text(
chat_id=call.message.chat.id, chat_id=call.message.chat.id,
message_id=call.message.id, message_id=call.message.id,
text=textbook.queue_users_list.format(name=queue.name, users_str=users_str), text=textbook.delete_queue_approve,
reply_markup=keyboards.queue_users(queue.id), reply_markup=keyboards.approve_queue_delete(),
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) await bot.answer_callback_query(callback_query_id=call.id)
@ -242,25 +388,18 @@ async def get_queue_link_handler(call: types.CallbackQuery):
@bot.callback_query_handler(func=lambda c: c.data == "delete_queue") @bot.callback_query_handler(func=lambda c: c.data == "delete_queue")
async def delete_queue_handler(call: types.CallbackQuery): async def delete_queue_handler(call: types.CallbackQuery):
if queue := await get_queue_from_state_data(call): if queue := await get_queue_from_state_data(call):
await bot.edit_message_text( for qu in queue.users:
chat_id=call.message.chat.id, session.delete(qu) # TODO: Use SQLAlchemy to cascade-delete all users
message_id=call.message.id, session.delete(queue)
text=textbook.queue_users_list.format(name=queue.name, users_str=users_str), session.commit()
reply_markup=keyboards.queue_users(queue.id), await bot.answer_callback_query(
parse_mode='html', callback_query_id=call.id, text=textbook.queue_deleted
) )
await my_queues_handler(call)
await bot.answer_callback_query(callback_query_id=call.id) await bot.answer_callback_query(callback_query_id=call.id)
@bot.callback_query_handler(func=lambda c: c.data == "settings") # User 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(),
)
@bot.callback_query_handler(func=lambda c: c.data == "edit_name") @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) @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: if len(msg.text) > 40 or "\n" in msg.text:
await bot.send_message(chat_id=msg.chat.id, text=textbook.edit_name_error) await bot.send_message(chat_id=msg.chat.id, text=textbook.edit_name_error)
return None return None
@ -291,37 +430,15 @@ async def update_name(msg: Message):
session.commit() session.commit()
await bot.send_message(chat_id=msg.chat.id, text=textbook.edit_name_success) await bot.send_message(chat_id=msg.chat.id, text=textbook.edit_name_success)
await asyncio.sleep(1) await asyncio.sleep(1)
await start(msg) await bot.set_state(user_id=msg.from_user.id, state=States.default)
@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( await bot.send_message(
chat_id=msg.chat.id, text=f"Пользователи этой очереди: {', '.join(users)}" chat_id=msg.chat.id,
text=textbook.settings,
reply_markup=keyboards.settings(),
) )
else:
await bot.send_message(chat_id=msg.chat.id, text="Очередь не найдена!")
# Other
@bot.message_handler(commands=["mystate"]) @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) await bot.send_message(chat_id=msg.from_user.id, text=state)
@bot.callback_query_handler(func=lambda c: c.data == "about") # Launch
async def about_handler(call: types.CallbackQuery):
await bot.answer_callback_query(
callback_query_id=call.id,
text=textbook.about,
show_alert=True,
)
async def main(): async def main():

View File

@ -1,7 +1,7 @@
from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column, Integer, String, BigInteger, ForeignKey from sqlalchemy import Column, Integer, String, BigInteger, ForeignKey
from sqlalchemy.dialects.postgresql import UUID from sqlalchemy.dialects.postgresql import UUID
from sqlalchemy.orm import relationship from sqlalchemy.orm import relationship, backref
import uuid import uuid
@ -12,8 +12,8 @@ class User(Base):
__tablename__ = "user" __tablename__ = "user"
id = Column(BigInteger, primary_key=True) id = Column(BigInteger, primary_key=True)
name = Column(String) name = Column(String(64))
username = Column(String) username = Column(String(32))
owns_queues = relationship("Queue", backref="owner") owns_queues = relationship("Queue", backref="owner")
takes_part_in_queues = relationship("QueueUser", backref="user") takes_part_in_queues = relationship("QueueUser", backref="user")
@ -23,10 +23,13 @@ class Queue(Base):
__tablename__ = "queue" __tablename__ = "queue"
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) 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")) 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): class QueueUser(Base):

View File

@ -26,10 +26,34 @@ def queue_menu() -> keyboard:
return keyboard( return keyboard(
keyboard=[ keyboard=[
[button(text="🔗 Ссылка для вступления", callback_data="get_queue_link")], [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="get_queue_users")],
[button(text="❌ Удалить очередь", callback_data="delete_queue")], [button(text="⚙️ Настройки очереди", callback_data="queue_settings")],
[button(text="⬅️ В меню", callback_data="to_menu")], [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: def queue_users(queue_id: str) -> keyboard:
return keyboard( return keyboard(
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}")], [button(text="⬅️ Назад", callback_data=f"q:{queue_id}")],
] ]
) )

View File

@ -2,13 +2,13 @@ start = "Привет! Я помогу тебе вести очередност
start_group = "Привет, я QUEUEBOT 2.0, помогаю создавать очереди в твоих групповых чатах!\n\nЧтобы пользоваться мной в этом чате, пользователь с правами админа должен настроить меня, комманда /settings, также не забудь выдать мне права администратора, чтобы я мог видеть список участников этой группы!" start_group = "Привет, я QUEUEBOT 2.0, помогаю создавать очереди в твоих групповых чатах!\n\nЧтобы пользоваться мной в этом чате, пользователь с правами админа должен настроить меня, комманда /settings, также не забудь выдать мне права администратора, чтобы я мог видеть список участников этой группы!"
menu = "Привет, {name}! Ты в главном меню" menu = "Привет, {name}! Ты в главном меню"
new_queue_created = ( new_queue_created = (
'Создана новая очередь: {id}. Редактировать ее можно в меню " Новая очередь"' "Создана новая очередь: {id}\n\nЗаходи в меню очередей и отправляй приглашения!"
) )
queue_limit = "Ты достиг лимита очередей (4). Удали свои очереди, или воспользуйся другим аккаунтом!" queue_limit = "Ты достиг лимита очередей (4). Удали свои очереди, или воспользуйся другим аккаунтом!"
my_queues_list = "У тебя {count} очередь/и/ей" my_queues_list = "У тебя {count} очередь/и/ей"
queue_stats = "Название: <b>{name}</b>\nКоличество участников: <b>{count}</b>" queue_stats = "Название: <b>{name}</b>\nКоличество участников: <b>{count}</b>"
error = "Произошла непредвиденная ошибка!" error = "Произошла непредвиденная ошибка!"
queue_operational_error = "Произошла ошибка! Либо вы не являетесь владельцем очереди, либо данные устарели и вам следует заново выбрать очередь в меню!" queue_operational_error = "Произошла ошибка! Либо ты не владельц очереди, либо данные устарели и вам следует заново выбрать очередь в меню!"
edit_queue_name = "Введи новое имя очереди, имя должно быть не длинее 40 символов и не должно содержать переносов строки" edit_queue_name = "Введи новое имя очереди, имя должно быть не длинее 40 символов и не должно содержать переносов строки"
settings = "🛠 Меню настроек" settings = "🛠 Меню настроек"
edit_name = 'Ты можешь поменять свое имя, которое будет отображаться в очередях. По умолчанию используется твое имя в Телеграме. Имя должно быть не длинее 40 символов и не иметь переносов строки. Пришли мне новое имя, или нажми кнопку "❌ Отмена"' edit_name = 'Ты можешь поменять свое имя, которое будет отображаться в очередях. По умолчанию используется твое имя в Телеграме. Имя должно быть не длинее 40 символов и не иметь переносов строки. Пришли мне новое имя, или нажми кнопку "❌ Отмена"'
@ -17,8 +17,18 @@ edit_queue_name_success = "Имя очереди изменено!"
edit_name_error = ( edit_name_error = (
'Новое имя не подходит под условия. Напиши подходящее, или нажми кнопку "❌ Отмена"' 'Новое имя не подходит под условия. Напиши подходящее, или нажми кнопку "❌ Отмена"'
) )
queue_settings = "🛠 Настройки очереди {name}"
delete_queue_approve = "Ты собираешься удалить очередь, все участники будут распущены. Подтвердить удаление?"
queue_deleted = "Очередь удалена"
queue_users_list = "В очереди <b>{name}</b> следующие участники:\n\n{users_str}" queue_users_list = "В очереди <b>{name}</b> следующие участники:\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 = "Ты присоединился к очереди <b>{name}</b>\nТвоя позиция: <b>{position}</b>\n\nКогда придет твоя очередь, я сообщу" joined_queue = "Ты присоединился к очереди <b>{name}</b>\nТвоя позиция: <b>{position}</b>\n\nКогда придет твоя очередь, я сообщу"
error_joining_queue = "Ты не можешь присоединиться к очереди <b>{name}</b>, так как ты уже в ней состоишь!"
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 = "Функция в разработке ¯\_(ツ)_/¯"