From 8b9298b34cf1c7e4f6f36d0917f8730400fe1854 Mon Sep 17 00:00:00 2001 From: Artem Reznichenko Date: Tue, 14 Nov 2023 16:15:21 +0300 Subject: [PATCH] second one --- .gitignore | 1 + bot/app/bot.py | 208 ++++++++++++++++++++++++++++++++++--------- bot/app/db/models.py | 12 +-- bot/app/keyboards.py | 10 +++ bot/app/textbook.py | 12 +++ docker-compose.yml | 2 + 6 files changed, 199 insertions(+), 46 deletions(-) diff --git a/.gitignore b/.gitignore index 2a16037..63c90ae 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ config.py *.pyc **/__pycache__ +**/.state-save \ No newline at end of file diff --git a/bot/app/bot.py b/bot/app/bot.py index beebab7..9b37953 100644 --- a/bot/app/bot.py +++ b/bot/app/bot.py @@ -35,22 +35,18 @@ session: Session = None class States(StatesGroup): default = State() newfund_amount = State() + close_fund = State() # Utils def get_fund_text(fund: Fund): - count = len(fund.members) - contributors = len(list(filter(lambda m: m.contributed, fund.members))) + count = fund.members.count() + contributors = fund.members.filter(FundMember.contributed).count() personal_amount = math.ceil(fund.amount / count) - return ( - "🟢 {name}\n\n", - "💵 Сумма: {amount}р\n\n", - "Каждый скидывает по {personal_amount}\n", - "Уже собрано: {collected_amount}\n\n", - "👥 Скинули: {contributors}/{count} чел.", - ).format( + return textbook.fund.format( + active="🟢" if fund.active else "🔴", name=fund.name, amount=fund.amount, personal_amount=personal_amount, @@ -74,7 +70,7 @@ def get_user(tg_user: TgUser) -> User: def get_group(tg_chat: TgChat) -> Group: - if group := session.query(User).filter(User.id == tg_chat.id).first(): + if group := session.query(Group).filter(Group.id == tg_chat.id).first(): return group group = Group( id=tg_chat.id, @@ -86,6 +82,7 @@ def get_group(tg_chat: TgChat) -> Group: # Bot logic + @bot.message_handler(commands=["start"]) async def start(msg: Message): if msg.chat.type == "private": @@ -103,7 +100,10 @@ async def start(msg: Message): # session.add(user) # session.commit() user = get_user(msg.from_user) - await bot.send_message(chat_id=msg.chat.id, text=textbook.start_private.format(count=len(user.fund_members))) + await bot.send_message( + chat_id=msg.chat.id, + text=textbook.start_private.format(count=user.fund_members.count()), + ) elif msg.chat.type in ("group", "supergroup"): get_group(msg.chat) @@ -122,7 +122,7 @@ async def setup(msg: Message): @bot.callback_query_handler(func=lambda c: c.data == "register_group_member") async def register_group_member(call: types.CallbackQuery): new = False - user = get_user(call.from_user.id) + get_user(call.from_user) if ( group_member := session.query(GroupMember) .filter( @@ -150,34 +150,36 @@ async def register_group_member(call: types.CallbackQuery): @bot.message_handler(commands=["newfund"]) async def newfund(msg: Message): if msg.chat.type in ("group", "supergroup"): - if session.query(Group).filter(Group.id == msg.chat.id).first(): - if ( - session.query(Fund) - .filter(Fund.group_id == msg.chat.id, Fund.active == True) - .first() - ): - await bot.send_message( - chat_id=msg.chat.id, - text=textbook.newfund_already_exists, - ) + if group := get_group(msg.chat): + if group.group_members.count(): + if ( + session.query(Fund) + .filter(Fund.group_id == msg.chat.id, Fund.active) + .first() + ): + await bot.send_message( + chat_id=msg.chat.id, + text=textbook.newfund_already_exists, + ) + else: + await bot.set_state( + user_id=msg.from_user.id, + chat_id=msg.chat.id, + state=States.newfund_amount, + ) + await bot.send_message( + chat_id=msg.chat.id, + text=textbook.newfund_amount.format( + user=user_link(msg.from_user) + ), + reply_markup=keyboards.cancel(), + parse_mode="html", + ) else: - await bot.set_state( - user_id=msg.from_user.id, - chat_id=msg.chat.id, - state=States.newfund_amount, - ) await bot.send_message( chat_id=msg.chat.id, - text=textbook.newfund_amount.format( - user=user_link(msg.from_user)), - reply_markup=keyboards.cancel(), - parse_mode="html", + text=textbook.not_set_up, ) - else: - await bot.send_message( - chat_id=msg.chat.id, - text=textbook.not_initialized - ) @bot.callback_query_handler( @@ -187,6 +189,7 @@ async def cancel_newfund_amount(call: types.CallbackQuery): await bot.set_state( user_id=call.from_user.id, chat_id=call.message.chat.id, state=States.default ) + await bot.delete_message(chat_id=call.message.chat.id, message_id=call.message.id) await bot.answer_callback_query( callback_query_id=call.id, text=textbook.cancel, @@ -208,15 +211,13 @@ async def newfund_amount(msg: Message): user_id=msg.from_user.id, chat_id=msg.chat.id, state=States.default ) fund = Fund( - owner_id=msg.from_user.id, group_id=msg.chat.id, amount=int( - msg.text) + owner_id=msg.from_user.id, group_id=msg.chat.id, amount=int(msg.text) ) session.add(fund) for group_member in session.query(GroupMember).filter( GroupMember.group_id == msg.chat.id ): - fund_member = FundMember( - user_id=group_member.user.id, fund_id=fund.id) + fund_member = FundMember(user_id=group_member.user.id, fund_id=fund.id) session.add(fund_member) session.commit() await bot.send_message( @@ -228,6 +229,7 @@ async def newfund_amount(msg: Message): chat_id=msg.chat.id, text=get_fund_text(fund), reply_markup=keyboards.fund_markup(), + parse_mode="html", ) else: @@ -241,6 +243,132 @@ async def newfund_amount(msg: Message): @bot.message_handler(commands=["fund"]) async def fund(msg: Message): + if msg.chat.type in ("group", "supergroup"): + group = get_group(msg.chat) + if ( + fund := session.query(Fund) + .filter(Fund.group_id == group.id, Fund.active == True) + .first() + ): + await bot.send_message( + chat_id=msg.chat.id, + text=get_fund_text(fund), + reply_markup=keyboards.fund_markup(), + parse_mode="html", + ) + else: + await bot.send_message( + chat_id=msg.chat.id, + text=textbook.fund_not_found, + ) + + +@bot.callback_query_handler(func=lambda c: c.data == "close_fund") +async def close_fund_prompt(call: types.CallbackQuery): + if group := get_group(call.message.chat): + if fund := group.funds.filter(Fund.active).first(): + if call.from_user.id == fund.owner_id: + await bot.edit_message_text( + text=textbook.close_fund_prompt, + chat_id=call.message.chat.id, + message_id=call.message.id, + reply_markup=keyboards.yes_no(), + parse_mode="html", + ) + await bot.set_state( + user_id=call.from_user.id, + chat_id=call.message.chat.id, + state=States.close_fund, + ) + else: + await bot.answer_callback_query( + callback_query_id=call.id, + text=textbook.not_owner.format(owner_name=fund.owner.name), + ) + else: + await bot.send_message( + chat_id=call.message.chat.id, + text=textbook.fund_not_found, + ) + + +@bot.callback_query_handler( + func=lambda c: c.data in ("yes", "no"), state=States.close_fund +) +async def close_fund(call: types.CallbackQuery): + if group := get_group(call.message.chat): + if fund := group.funds.filter(Fund.active).first(): + if call.from_user.id == fund.owner_id: + if call.data == "yes": + setattr(fund, "active", False) + session.commit() + await bot.edit_message_text( + text=textbook.fund_closed.format( + name=fund.name, fund_text=get_fund_text(fund) + ), + chat_id=call.message.chat.id, + message_id=call.message.id, + parse_mode="html", + ) + elif call.data == "no": + await bot.edit_message_text( + text=get_fund_text(fund), + chat_id=call.message.chat.id, + message_id=call.message.id, + reply_markup=keyboards.fund_markup(), + parse_mode="html", + ) + else: + await bot.answer_callback_query( + callback_query_id=call.id, + text=textbook.not_owner.format(owner_name=fund.owner.name), + ) + else: + await bot.send_message( + chat_id=call.message.chat.id, + text=textbook.fund_not_found, + ) + + +@bot.callback_query_handler(func=lambda c: c.data == "contributed") +async def contributed(call: types.CallbackQuery): + group = get_group(call.message.chat) + user = get_user(call.from_user) + if fund := group.funds.filter(Fund.active).first(): + if fund_user := fund.members.filter( + FundMember.user_id == call.from_user.id + ).first(): + if not fund_user.contributed: + setattr(fund_user, "contributed", True) + session.commit() + await bot.answer_callback_query( + callback_query_id=call.id, + text=textbook.contributed, + ) + await bot.edit_message_text( + text=get_fund_text(fund), + chat_id=call.message.chat.id, + message_id=call.message.id, + reply_markup=keyboards.fund_markup(), + parse_mode="html", + ) + else: + await bot.answer_callback_query( + callback_query_id=call.id, + text=textbook.already_contributed, + show_alert=True, + ) + + else: + await bot.answer_callback_query( + callback_query_id=call.id, + text=textbook.not_fund_member, + show_alert=True, + ) + else: + await bot.answer_callback_query( + callback_query_id=call.id, text=textbook.fund_not_found, show_alert=True + ) @bot.message_handler(commands=["mystate"]) diff --git a/bot/app/db/models.py b/bot/app/db/models.py index 42c3298..04baa0d 100644 --- a/bot/app/db/models.py +++ b/bot/app/db/models.py @@ -16,9 +16,9 @@ class User(Base): name = Column(String(64)) username = Column(String(32)) - owns_funds = relationship("Fund", backref="owner") - fund_members = relationship("FundMember", backref="user") - group_members = relationship("GroupMember", backref="user") + owns_funds = relationship("Fund", backref="owner", lazy="dynamic") + fund_members = relationship("FundMember", backref="user", lazy="dynamic") + group_members = relationship("GroupMember", backref="user", lazy="dynamic") class Group(Base): @@ -26,9 +26,9 @@ class Group(Base): id = Column(BigInteger, primary_key=True) - funds = relationship("Fund", backref="group") + funds = relationship("Fund", backref="group", lazy="dynamic") - group_members = relationship("GroupMember", backref="group") + group_members = relationship("GroupMember", backref="group", lazy="dynamic") class GroupMember(Base): @@ -52,7 +52,7 @@ class Fund(Base): amount = Column(Integer) active = Column(Boolean, default=True) - members = relationship("FundMember", backref="fund") + members = relationship("FundMember", backref="fund", lazy="dynamic") class FundMember(Base): diff --git a/bot/app/keyboards.py b/bot/app/keyboards.py index 190e9e6..4d84933 100644 --- a/bot/app/keyboards.py +++ b/bot/app/keyboards.py @@ -23,6 +23,16 @@ def cancel() -> keyboard: def fund_markup() -> keyboard: return keyboard( keyboard=[ + [button(text="✅ Я скинул", callback_data="contributed")], [button(text="🏁 Завершить сбор", callback_data="close_fund")], ] ) + + +def yes_no() -> keyboard: + return keyboard( + keyboard=[ + [button(text="✅ Да", callback_data="yes")], + [button(text="❌ Нет", callback_data="no")], + ] + ) diff --git a/bot/app/textbook.py b/bot/app/textbook.py index 58517ae..fefc09c 100644 --- a/bot/app/textbook.py +++ b/bot/app/textbook.py @@ -15,3 +15,15 @@ not_number = 'Вы ввели не число. {user}, напишите сумм fund_created = "Создан новый сбор: {fund}" fund_not_found = "На данный момент в этом чате сборов нет! Создать новый - /newfund" + + +fund = "{active} {name}\n\n💵 Сумма: {amount}р\n\nКаждый скидывает по {personal_amount}р\nУже собрано: {collected_amount}р\n👥 Скинули: {contributors}/{count} чел." + +close_fund_prompt = "Вы уверены? Это завершит активный в данный момент сбор!" +fund_closed = 'Сбор "{name}" закрыт! Вот его данные:\n\n{fund_text}' +not_owner = "Вы не являетесь создателем этого сбора, обратитесь к {owner_name}, если хотите его закрыть" +not_set_up = "В группе меценатов нет ни одного участника! Вы точно прописывали /setup и все желающие участвовать в сборах приняли участие?" +not_fund_member = "Вы не являетесь участником этого сбора! Пропишите /setup и попросите создать новый сбор!" + +contributed = "Вы отметились!" +already_contributed = "Вы уже отмечались ранее. Не забудьте уведомить создателя сбора, если вы решили отказаться от участия!" diff --git a/docker-compose.yml b/docker-compose.yml index e1e8130..45d4872 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -23,6 +23,8 @@ services: DB_NAME: db HOST: postgres PORT: 5432 + volumes: + - ./.state-save:/app/.state-save/:rw depends_on: postgres: condition: service_healthy