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