not working shit
This commit is contained in:
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
**/postgres_data
|
||||
config.py
|
||||
*.pyc
|
||||
**/__pycache__
|
||||
7
bot/Dockerfile
Normal file
7
bot/Dockerfile
Normal file
@ -0,0 +1,7 @@
|
||||
FROM python:3.11.3-alpine
|
||||
WORKDIR /app
|
||||
COPY requirements.txt .
|
||||
RUN pip install -r requirements.txt
|
||||
ADD app/ .
|
||||
ENTRYPOINT ["python"]
|
||||
CMD ["/app/bot.py"]
|
||||
227
bot/app/bot.py
Normal file
227
bot/app/bot.py
Normal file
@ -0,0 +1,227 @@
|
||||
# Telebot imports
|
||||
from telebot.async_telebot import AsyncTeleBot
|
||||
from telebot.asyncio_storage import StateMemoryStorage, StatePickleStorage
|
||||
from telebot.asyncio_handler_backends import State, StatesGroup
|
||||
from telebot.asyncio_filters import StateFilter
|
||||
from telebot import types
|
||||
from telebot.callback_data import CallbackData, CallbackDataFilter
|
||||
from telebot.types import Message
|
||||
from telebot.util import user_link
|
||||
|
||||
# Async things imports
|
||||
import asyncio
|
||||
|
||||
# Other modules imports
|
||||
from datetime import datetime
|
||||
import math
|
||||
from typing import Union
|
||||
|
||||
# Local imports
|
||||
from config import token
|
||||
import textbook
|
||||
import keyboards
|
||||
|
||||
# DB
|
||||
from db.base import Session, engine, Base
|
||||
from db.models import User, Group, GroupMember, Fund, FundMember
|
||||
|
||||
|
||||
bot = AsyncTeleBot(token, state_storage=StatePickleStorage())
|
||||
session: Session = None
|
||||
|
||||
|
||||
class States(StatesGroup):
|
||||
default = State()
|
||||
newfund_amount = State()
|
||||
|
||||
|
||||
# Utils
|
||||
|
||||
|
||||
def get_fund_text(fund: Fund):
|
||||
count = len(fund.fund_members)
|
||||
contributors = len(fund.fund_members.filter(FundMember.contributed == True))
|
||||
personal_amount = math.ceil(fund.amount / count)
|
||||
return (
|
||||
"🟢 {name}\n\n",
|
||||
"💵 Сумма: {amount}р\n\n",
|
||||
"<b>Каждый скидывает по {personal_amount}</b>\n",
|
||||
"Уже собрано: {collected_amount}\n\n",
|
||||
"👥 Скинули: {contributors}/{count} чел.",
|
||||
).format(
|
||||
name=fund.name,
|
||||
amount=fund.amount,
|
||||
personal_amount=personal_amount,
|
||||
collected_amount=personal_amount * contributors,
|
||||
contributors=contributors,
|
||||
count=count,
|
||||
)
|
||||
|
||||
|
||||
@bot.message_handler(commands=["start"])
|
||||
async def start(msg: Message):
|
||||
if msg.chat.type == "private":
|
||||
if user := session.query(User).filter(User.id == msg.chat.id).first():
|
||||
await bot.send_message(
|
||||
chat_id=msg.chat.id,
|
||||
text=textbook.private_info.format(count=len(user.fund_members)),
|
||||
)
|
||||
else:
|
||||
user = User(
|
||||
id=msg.chat.id,
|
||||
name=msg.from_user.first_name,
|
||||
username=msg.from_user.username,
|
||||
)
|
||||
session.add(user)
|
||||
session.commit()
|
||||
await bot.send_message(chat_id=msg.chat.id, text=textbook.start_private)
|
||||
|
||||
elif msg.chat.type in ("group", "supergroup"):
|
||||
if not session.query(Group).filter(Group.id == msg.chat.id).first():
|
||||
group = Group(id=msg.chat.id)
|
||||
session.add(group)
|
||||
session.commit()
|
||||
await bot.send_message(chat_id=msg.chat.id, text=textbook.start_group)
|
||||
|
||||
|
||||
@bot.message_handler(commands=["setup"])
|
||||
async def setup(msg: Message):
|
||||
if msg.chat.type in ("group", "supergroup"):
|
||||
await bot.send_message(
|
||||
chat_id=msg.chat.id, text=textbook.setup, reply_markup=keyboards.setup()
|
||||
)
|
||||
|
||||
|
||||
@bot.callback_query_handler(func=lambda c: c.data == "register_group_member")
|
||||
async def register_group_member(call: types.CallbackQuery):
|
||||
new = False
|
||||
if not session.query(User).filter(User.id == call.from_user.id).first():
|
||||
user = User(
|
||||
id=call.from_user.id,
|
||||
name=call.from_user.first_name,
|
||||
username=call.from_user.username,
|
||||
)
|
||||
session.add(user)
|
||||
if (
|
||||
group_member := session.query(GroupMember)
|
||||
.filter(
|
||||
GroupMember.group_id == call.message.chat.id,
|
||||
GroupMember.user_id == call.from_user.id,
|
||||
)
|
||||
.first()
|
||||
):
|
||||
session.delete(group_member)
|
||||
else:
|
||||
group_member = GroupMember(
|
||||
group_id=call.message.chat.id,
|
||||
user_id=call.from_user.id,
|
||||
)
|
||||
session.add(group_member)
|
||||
new = True
|
||||
session.commit()
|
||||
await bot.answer_callback_query(
|
||||
callback_query_id=call.id,
|
||||
text=textbook.user_parted if new else textbook.user_left,
|
||||
show_alert=True,
|
||||
)
|
||||
|
||||
|
||||
@bot.message_handler(commands=["newfund"])
|
||||
async def newfund(msg: Message):
|
||||
if msg.chat.type in ("group", "supergroup"):
|
||||
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,
|
||||
)
|
||||
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",
|
||||
)
|
||||
|
||||
|
||||
@bot.callback_query_handler(
|
||||
func=lambda c: c.data == "cancel", state=States.newfund_amount
|
||||
)
|
||||
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.answer_callback_query(
|
||||
callback_query_id=call.id,
|
||||
text=textbook.cancel,
|
||||
)
|
||||
|
||||
|
||||
@bot.message_handler(func=lambda message: True)
|
||||
async def newfund_amount(msg: Message):
|
||||
await bot.send_message(chat_id=msg.chat.id, text="test")
|
||||
if msg.text.isdigit():
|
||||
await bot.set_state(
|
||||
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)
|
||||
)
|
||||
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)
|
||||
session.add(fund_member)
|
||||
session.commit()
|
||||
await bot.send_message(
|
||||
chat_id=msg.chat.id,
|
||||
text=textbook.fund_created.format(fund=fund.name),
|
||||
)
|
||||
await asyncio.sleep(1)
|
||||
await bot.send_message(
|
||||
chat_id=msg.chat.id,
|
||||
text=get_fund_text(fund),
|
||||
reply_markup=keyboards.fund_markup(),
|
||||
)
|
||||
|
||||
else:
|
||||
await bot.send_message(
|
||||
chat_id=msg.chat.id,
|
||||
text=textbook.not_number.format(user=user_link(msg.from_user)),
|
||||
reply_markup=keyboards.cancel(),
|
||||
parse_mode="html",
|
||||
)
|
||||
|
||||
|
||||
@bot.message_handler(commands=["mystate"])
|
||||
async def mystate(msg: Message):
|
||||
state = await bot.get_state(user_id=msg.from_user.id)
|
||||
await bot.send_message(chat_id=msg.chat.id, text=state)
|
||||
|
||||
|
||||
@bot.message_handler(commands=["chatid"])
|
||||
async def chatid(msg: Message):
|
||||
await bot.send_message(chat_id=msg.chat.id, text=msg.chat.id)
|
||||
|
||||
|
||||
async def main():
|
||||
a = asyncio.create_task(bot.polling(non_stop=True))
|
||||
await a
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
print("Bot started", flush=True)
|
||||
Base.metadata.create_all(engine)
|
||||
session = Session()
|
||||
bot.add_custom_filter(StateFilter(bot))
|
||||
bot.enable_saving_states(filename="./.state-save/states.pkl")
|
||||
asyncio.run(main())
|
||||
11
bot/app/db/base.py
Normal file
11
bot/app/db/base.py
Normal file
@ -0,0 +1,11 @@
|
||||
from sqlalchemy import create_engine
|
||||
from sqlalchemy.ext.declarative import declarative_base
|
||||
from sqlalchemy.exc import OperationalError
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
from db.settings import Settings
|
||||
|
||||
engine = create_engine(Settings().uri)
|
||||
|
||||
Session = sessionmaker(bind=engine)
|
||||
|
||||
Base = declarative_base()
|
||||
64
bot/app/db/models.py
Normal file
64
bot/app/db/models.py
Normal file
@ -0,0 +1,64 @@
|
||||
from sqlalchemy.ext.declarative import declarative_base
|
||||
from sqlalchemy import Column, Integer, String, BigInteger, ForeignKey, Boolean
|
||||
from sqlalchemy.dialects.postgresql import UUID
|
||||
from sqlalchemy.orm import relationship, backref
|
||||
|
||||
import uuid
|
||||
|
||||
from db.base import Base
|
||||
from datetime import date
|
||||
|
||||
|
||||
class User(Base):
|
||||
__tablename__ = "user"
|
||||
|
||||
id = Column(BigInteger, primary_key=True)
|
||||
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")
|
||||
|
||||
|
||||
class Group(Base):
|
||||
__tablename__ = "group"
|
||||
|
||||
id = Column(BigInteger, primary_key=True)
|
||||
|
||||
funds = relationship("Fund", backref="group")
|
||||
|
||||
group_members = relationship("GroupMember", backref="group")
|
||||
|
||||
|
||||
class GroupMember(Base):
|
||||
__tablename__ = "groupmember"
|
||||
|
||||
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
||||
user_id = Column(BigInteger, ForeignKey("user.id"))
|
||||
group_id = Column(BigInteger, ForeignKey("group.id"))
|
||||
|
||||
|
||||
class Fund(Base):
|
||||
__tablename__ = "fund"
|
||||
|
||||
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
||||
name = Column(
|
||||
String(40), default="Сбор {date}".format(date=date.today().strftime("%d.%m"))
|
||||
)
|
||||
description = Column(String(120), default=None)
|
||||
owner_id = Column(BigInteger, ForeignKey("user.id"))
|
||||
group_id = Column(BigInteger, ForeignKey("group.id"))
|
||||
amount = Column(Integer)
|
||||
active = Column(Boolean, default=True)
|
||||
|
||||
users = relationship("FundMember", backref="fund")
|
||||
|
||||
|
||||
class FundMember(Base):
|
||||
__tablename__ = "fundmember"
|
||||
|
||||
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
||||
user_id = Column(BigInteger, ForeignKey("user.id"))
|
||||
fund_id = Column(UUID(as_uuid=True), ForeignKey("fund.id"))
|
||||
contributed = Column(Boolean, default=False)
|
||||
17
bot/app/db/settings.py
Normal file
17
bot/app/db/settings.py
Normal file
@ -0,0 +1,17 @@
|
||||
from dataclasses import dataclass
|
||||
import os
|
||||
|
||||
|
||||
@dataclass
|
||||
class Settings:
|
||||
dialect: str = os.getenv("DIALECT", "postgresql")
|
||||
driver: str = os.getenv("DRIVER", "psycopg2")
|
||||
user: str = os.getenv("USER", "user")
|
||||
password: str = os.getenv("PASSWORD", "password")
|
||||
db_name: str = os.getenv("DB_NAME", "db")
|
||||
host: str = os.getenv("HOST", "postgres")
|
||||
port: int = os.getenv("PORT", 5432)
|
||||
|
||||
@property
|
||||
def uri(self) -> str:
|
||||
return f"{self.dialect}+{self.driver}://{self.user}:{self.password}@{self.host}:{self.port}/{self.db_name}"
|
||||
28
bot/app/keyboards.py
Normal file
28
bot/app/keyboards.py
Normal file
@ -0,0 +1,28 @@
|
||||
from telebot.types import (
|
||||
InlineKeyboardButton as button,
|
||||
InlineKeyboardMarkup as keyboard,
|
||||
)
|
||||
|
||||
|
||||
def setup() -> keyboard:
|
||||
return keyboard(
|
||||
keyboard=[
|
||||
[button(text="💸 Стать участником", callback_data="register_group_member")],
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
def cancel() -> keyboard:
|
||||
return keyboard(
|
||||
keyboard=[
|
||||
[button(text="❌ Отменить", callback_data="cancel")],
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
def fund_markup() -> keyboard:
|
||||
return keyboard(
|
||||
keyboard=[
|
||||
[button(text="🏁 Завершить сбор", callback_data="close_fund")],
|
||||
]
|
||||
)
|
||||
17
bot/app/textbook.py
Normal file
17
bot/app/textbook.py
Normal file
@ -0,0 +1,17 @@
|
||||
start_private = "Привет, спасибо, что активировали меня в личных сообщениях! Теперь я смогу уведомлять вас о сборах, в которых вы забыли принять участие!"
|
||||
private_info = "Вы принимаете участие в {count} сборах!"
|
||||
start_group = "Всем привет, я @waterfundbot! Я помогу собрать деньги на что угодно, уведомлю каждого о сборе в чате, напомню не скинувшим, и многое другое!\n\n Для начала админу чата необходимо прописать /setup. Появится сообщение, под которым будет кнопка - ее необходимо нажать всем, кто планирует участвовать в сборах в этом чате.\n\nНачать новый сбор /newfund\n\nТакже попрошу всех участвующих в сборах начать со мной диалог в личном чате, чтобы я мог уведомлять вас лично."
|
||||
|
||||
setup = "Все, кто планирует участвовать в сборах в этом чате, должны нажать на кнопочку ниже. Если вы передумали - нажмите еще раз, и вы откажетесь от участия."
|
||||
user_parted = "Вы приняли участие в сборах в этом чате!"
|
||||
user_left = "Вы отказались от участия в сборах в этом чате"
|
||||
|
||||
newfund_already_exists = "Предыдущий сбор все еще активен! Пропишите /fund, чтобы показать его, и завершите его, если необходимо создать новый!"
|
||||
newfund_amount = (
|
||||
'Отлично, новый сбор. {user}, напишите сумму сбора, или кнопку "❌ Отменить"'
|
||||
)
|
||||
cancel = "Хорошо, проехали"
|
||||
not_number = 'Вы ввели не число. {user}, напишите сумму сбора, или кнопку "❌ Отменить"'
|
||||
fund_created = "Создан новый сбор: {fund}"
|
||||
|
||||
fund_not_found = "На данный момент в этом чате сборов нет! Создать новый - /newfund"
|
||||
7
bot/requirements.txt
Normal file
7
bot/requirements.txt
Normal file
@ -0,0 +1,7 @@
|
||||
pytelegrambotapi
|
||||
asyncio
|
||||
aiohttp
|
||||
psycopg-binary
|
||||
pydantic
|
||||
sqlalchemy
|
||||
psycopg2-binary
|
||||
28
docker-compose.yml
Normal file
28
docker-compose.yml
Normal file
@ -0,0 +1,28 @@
|
||||
services:
|
||||
postgres:
|
||||
image: postgres:15.1
|
||||
volumes:
|
||||
- ./postgres_data:/var/lib/postgresql/data/:rw
|
||||
environment:
|
||||
POSTGRES_USER: user
|
||||
POSTGRES_PASSWORD: password
|
||||
POSTGRES_DB: db
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -d db --user user"]
|
||||
interval: 2s
|
||||
timeout: 2s
|
||||
retries: 5
|
||||
bot:
|
||||
build:
|
||||
context: bot
|
||||
environment:
|
||||
DIALECT: postgresql
|
||||
DRIVER: psycopg2
|
||||
USER: user
|
||||
PASSWORD: password
|
||||
DB_NAME: db
|
||||
HOST: postgres
|
||||
PORT: 5432
|
||||
depends_on:
|
||||
postgres:
|
||||
condition: service_healthy
|
||||
Reference in New Issue
Block a user