not working shit

This commit is contained in:
2023-11-12 22:43:47 +03:00
commit 9f76abcfc4
10 changed files with 410 additions and 0 deletions

4
.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
**/postgres_data
config.py
*.pyc
**/__pycache__

7
bot/Dockerfile Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View File

@ -0,0 +1,7 @@
pytelegrambotapi
asyncio
aiohttp
psycopg-binary
pydantic
sqlalchemy
psycopg2-binary

28
docker-compose.yml Normal file
View 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