0
app/__init__.py
Normal file
0
app/__init__.py
Normal file
66
app/config.py
Normal file
66
app/config.py
Normal file
@@ -0,0 +1,66 @@
|
||||
from dataclasses import dataclass
|
||||
from environs import Env
|
||||
|
||||
|
||||
@dataclass
|
||||
class DatabaseConfig:
|
||||
database: str
|
||||
host: str
|
||||
user: str
|
||||
password: str
|
||||
|
||||
|
||||
@dataclass
|
||||
class TgBot:
|
||||
token: str
|
||||
admin_ids: list[int]
|
||||
superadmin: int
|
||||
|
||||
|
||||
@dataclass
|
||||
class TableSchemas:
|
||||
main_table: str
|
||||
main_table_cols: str
|
||||
|
||||
|
||||
@dataclass
|
||||
class ChatBot:
|
||||
url: str
|
||||
api_key: str
|
||||
bot_model: str
|
||||
|
||||
|
||||
@dataclass
|
||||
class Config:
|
||||
tg_bot: TgBot
|
||||
db: DatabaseConfig
|
||||
table: TableSchemas
|
||||
chat_bot: ChatBot
|
||||
|
||||
|
||||
def config_loader(path: str | None = None) -> Config:
|
||||
env: Env = Env()
|
||||
env.read_env(path)
|
||||
# Загружаем конфигурацию из .env файла и возвращаем его экземпляром Config dataclass'а'
|
||||
return Config(
|
||||
tg_bot=TgBot(
|
||||
token=env('BOT_TOKEN'),
|
||||
admin_ids=list(map(int, env.list('ADMIN_IDS'))),
|
||||
superadmin=env('superadmin')
|
||||
),
|
||||
db=DatabaseConfig(
|
||||
database=env('DATABASE'),
|
||||
host=env('DB_HOST'),
|
||||
user=env('DB_USER'),
|
||||
password=env('DB_PASSWORD')
|
||||
),
|
||||
table=TableSchemas(
|
||||
main_table=env('MAIN_TABLE'),
|
||||
main_table_cols=env('MAIN_TABLE_COLS'),
|
||||
),
|
||||
chat_bot=ChatBot(
|
||||
url=env('CHAT_BOT_URL'),
|
||||
api_key=env('CHAT_BOT_API_KEY'),
|
||||
bot_model=env('BOT_MODEL')
|
||||
)
|
||||
)
|
5
app/database/__init__.py
Normal file
5
app/database/__init__.py
Normal file
@@ -0,0 +1,5 @@
|
||||
from .database_engine import async_session_
|
||||
from .models import Worker,Component, Order
|
||||
|
||||
|
||||
__all__ = ["Worker", "Component", "Order", "async_session_"]
|
14
app/database/database_engine.py
Normal file
14
app/database/database_engine.py
Normal file
@@ -0,0 +1,14 @@
|
||||
import os
|
||||
import asyncpg
|
||||
from sqlalchemy.ext.asyncio import async_sessionmaker, create_async_engine, session
|
||||
import dotenv
|
||||
|
||||
# connection = psycopg2.connect(*(os.getenv(key) for key in ["DATABASE", "DB_HOST", "DB_USER", "DB_PASSWORD"]))
|
||||
# connection.autocommit = True
|
||||
|
||||
dotenv.load_dotenv(".env")
|
||||
DATABASE_URL = (f"postgresql+asyncpg://{os.getenv('DB_USER')}:{os.getenv('DB_PASSWORD')}@"
|
||||
f"{os.getenv('DB_HOST')}:9432/{os.getenv('DATABASE')}")
|
||||
print(DATABASE_URL)
|
||||
engine = create_async_engine(DATABASE_URL, echo=True)
|
||||
async_session_ = async_sessionmaker(bind=engine, expire_on_commit=False)
|
89
app/database/models.py
Normal file
89
app/database/models.py
Normal file
@@ -0,0 +1,89 @@
|
||||
from sqlalchemy import Column, Integer, String, Date, ForeignKey, func, Null
|
||||
from sqlalchemy.dialects.postgresql import ENUM
|
||||
from sqlalchemy.orm import relationship, DeclarativeBase
|
||||
|
||||
status_enum = ENUM('Выполнено', 'В процессе', 'Создано', 'Ожидание комплектующих', name='status')
|
||||
|
||||
|
||||
class Base(DeclarativeBase):
|
||||
pass
|
||||
|
||||
|
||||
class Worker(Base):
|
||||
"""
|
||||
id SERIAL PRIMARY KEY,
|
||||
telegram_id INTEGER UNIQUE NOT NULL,
|
||||
name VARCHAR NOT NULL,
|
||||
email VARCHAR(50),
|
||||
phone_number VARCHAR(20) NOT NULL,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP
|
||||
"""
|
||||
__tablename__ = "workers"
|
||||
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
telegram_id = Column(Integer, unique=True, nullable=False)
|
||||
name = Column(String, nullable=False)
|
||||
email = Column(String, nullable=True)
|
||||
phone_number = Column(String, nullable=False)
|
||||
created_at = Column(Date, server_default=func.now())
|
||||
updated_at = Column(Date, onupdate=func.now())
|
||||
|
||||
|
||||
class Order(Base):
|
||||
"""
|
||||
id SERIAL PRIMARY KEY,
|
||||
name VARCHAR,
|
||||
worker_id INTEGER REFERENCES workers (id),
|
||||
status_id status DEFAULT 'Создано',
|
||||
counterparty VARCHAR(50),
|
||||
customer VARCHAR NOT NULL,
|
||||
commencement_work DATE,
|
||||
end_work DATE,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
description VARCHAR DEFAULT NULL
|
||||
"""
|
||||
__tablename__ = "orders"
|
||||
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
name = Column(String)
|
||||
worker_id = Column(Integer, ForeignKey('workers.telegram_id'), nullable=False)
|
||||
status_id = Column(status_enum)
|
||||
counterparty = Column(String)
|
||||
customer = Column(String, nullable=False)
|
||||
commencement_work = Column(Date, nullable=True)
|
||||
end_work = Column(Date, nullable=True)
|
||||
created_at = Column(Date, server_default=func.now())
|
||||
description = Column(String, default=Null)
|
||||
user = relationship("Worker", backref="orders")
|
||||
|
||||
|
||||
class Component(Base):
|
||||
"""
|
||||
id SERIAL PRIMARY KEY,
|
||||
name VARCHAR NOT NULL,
|
||||
description VARCHAR NULL
|
||||
"""
|
||||
__tablename__ = "components"
|
||||
|
||||
id = Column(Integer, primary_key=True)
|
||||
name = Column(String, nullable=False)
|
||||
description = Column(String, default=Null)
|
||||
|
||||
|
||||
class OrderComponent(Base):
|
||||
"""
|
||||
id SERIAL PRIMARY KEY,
|
||||
order_id INTEGER REFERENCES orders (id),
|
||||
component_id INTEGER REFERENCES components (id),
|
||||
quantity INTEGER DEFAULT 1
|
||||
"""
|
||||
__tablename__ = "order_components"
|
||||
|
||||
id = Column(Integer, primary_key=True)
|
||||
order_id = Column(Integer, ForeignKey('orders.id'))
|
||||
component_id = Column(Integer, ForeignKey('components.id'))
|
||||
quantity = Column(Integer, default=1)
|
||||
|
||||
order = relationship("Order", backref="order_components")
|
||||
component = relationship("Component", backref="order_components")
|
25
app/filters/Filters.py
Normal file
25
app/filters/Filters.py
Normal file
@@ -0,0 +1,25 @@
|
||||
import logging
|
||||
from aiogram.types import Message, CallbackQuery
|
||||
from aiogram.filters import BaseFilter
|
||||
from keyboards.menu_commands import commands
|
||||
import os
|
||||
|
||||
loggger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
|
||||
class IsAdmin(BaseFilter):
|
||||
def __init__(self):
|
||||
self.admins_ids = os.getenv("BOT_ADMINS").split(",")
|
||||
|
||||
async def __call__(self, message: Message | CallbackQuery) -> bool:
|
||||
return str(message.from_user.id) in self.admins_ids
|
||||
|
||||
|
||||
class CommandFilter(BaseFilter):
|
||||
def __init__(self):
|
||||
self.commands = commands
|
||||
|
||||
async def __call__(self, message: Message) -> bool:
|
||||
return message.text.startswith(tuple(self.commands.keys()))
|
||||
|
1
app/filters/__init__.py
Normal file
1
app/filters/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
from .Filters import IsAdmin
|
6
app/handlers/__init__.py
Normal file
6
app/handlers/__init__.py
Normal file
@@ -0,0 +1,6 @@
|
||||
from .admin import admin_router
|
||||
from .registration import registration_router
|
||||
from .orders import orders_router
|
||||
from .components import components_router
|
||||
|
||||
__all__ = ["admin_router", "orders_router", "registration_router", "components_router"]
|
34
app/handlers/admin.py
Normal file
34
app/handlers/admin.py
Normal file
@@ -0,0 +1,34 @@
|
||||
import time
|
||||
import re
|
||||
from aiogram import Router, Bot, F
|
||||
from aiogram.types import (Message, ChatMemberUpdated, FSInputFile, CallbackQuery, ReplyKeyboardRemove)
|
||||
from loguru import logger
|
||||
from handlers.registration import registration_confirm
|
||||
from filters.Filters import IsAdmin, CommandFilter
|
||||
from database import async_session_
|
||||
|
||||
admin_router = Router()
|
||||
|
||||
admin_router.message.filter(IsAdmin())
|
||||
|
||||
|
||||
regex = re.compile(r'(del|reg) @.+')
|
||||
|
||||
|
||||
@admin_router.callback_query(lambda x: re.fullmatch(regex, x.data))
|
||||
async def reg_del_command(callback: CallbackQuery, bot: Bot):
|
||||
logger.warning(f'Received command: {callback.data}')
|
||||
new_user_id = int(re.search(r'\d+', callback.data).group())
|
||||
if callback.data.startswith('reg'):
|
||||
registration_confirm[new_user_id].set()
|
||||
await callback.answer("Новый пользователь зарегистрирован")
|
||||
await callback.message.delete()
|
||||
|
||||
|
||||
@admin_router.message(F.text.startswith('@msg'))
|
||||
async def send_message_command(message: Message, bot: Bot):
|
||||
chat_id = re.search(r'(\d+)', message.text).group()
|
||||
print(chat_id)
|
||||
await bot.send_message(text='Ronis->' + message.text.strip('@msg_' + chat_id), chat_id=chat_id)
|
||||
|
||||
|
9
app/handlers/components.py
Normal file
9
app/handlers/components.py
Normal file
@@ -0,0 +1,9 @@
|
||||
from aiogram import Router
|
||||
from aiogram.filters import Command
|
||||
from aiogram.types import Message
|
||||
|
||||
components_router = Router()
|
||||
@components_router.message(Command(commands="components"))
|
||||
async def components(message: Message):
|
||||
await message.answer("Функция пока не доступна")
|
||||
await message.delete()
|
230
app/handlers/orders.py
Normal file
230
app/handlers/orders.py
Normal file
@@ -0,0 +1,230 @@
|
||||
import asyncio
|
||||
import os
|
||||
from pathlib import Path
|
||||
import re
|
||||
from aiogram import Router, Bot, F
|
||||
from aiogram.filters import CommandStart, Command
|
||||
from aiogram.types import Message, CallbackQuery, FSInputFile, InputMediaPhoto, InputMediaVideo
|
||||
from aiogram.exceptions import AiogramError
|
||||
from aiogram.fsm.state import State, StatesGroup
|
||||
from aiogram.fsm.context import FSMContext
|
||||
from sqlalchemy import select, insert
|
||||
from loguru import logger
|
||||
from filters import IsAdmin
|
||||
from keyboards import create_inline_kb, commands, button_create
|
||||
from database import async_session_, Order, Worker
|
||||
|
||||
orders_router = Router()
|
||||
|
||||
# orders_router.message.filter()
|
||||
|
||||
order_operation_base = {"add_order_photo": "Добавить фото",
|
||||
"get_order_photo": "Получить фото",
|
||||
"get_order_components": "Получить список комплектующих",
|
||||
"get_order_documentation": "Получить документацию"
|
||||
}
|
||||
order_operation_update = {"add_order_documentation": "Добавить документацию"}
|
||||
|
||||
order_main = {"find_orders": "Найти заказ"}
|
||||
order_main_update = {"create_order": "Создать заказ"}
|
||||
|
||||
find_order_params = {"search_by_name": "Поиск по названию", "search_by_description": "Поиск по описанию ",
|
||||
"search_by_id": "Поиск по номеру заказа", "search_by_customer": "Поиск по заказчику"}
|
||||
|
||||
|
||||
class SearchForm(StatesGroup):
|
||||
search_option = State()
|
||||
data_to_search = State()
|
||||
search_result = State()
|
||||
|
||||
|
||||
class OrderForm(StatesGroup):
|
||||
id = State()
|
||||
worker_id = State()
|
||||
status_id = State()
|
||||
counterparty = State()
|
||||
customer = State()
|
||||
commencement_work = State()
|
||||
end_work = State()
|
||||
|
||||
description = State()
|
||||
|
||||
|
||||
@orders_router.message(Command(commands="orders"))
|
||||
async def orders_menu(message: Message):
|
||||
order_main_upd = order_main_update if await IsAdmin()(message) else {}
|
||||
await message.answer(text="Доступные действия с заказами:",
|
||||
reply_markup=create_inline_kb(width=1, **order_main, **order_main_upd))
|
||||
|
||||
|
||||
@orders_router.callback_query(lambda x: x.data.startswith("create_order"))
|
||||
async def get_order_worker_id(callback: CallbackQuery, state: FSMContext):
|
||||
await state.set_state(OrderForm.worker_id)
|
||||
await callback.message.answer("Введите id сборщика который будет собирать заказ:",
|
||||
reply_markup=create_inline_kb(**{f"{callback.from_user.id}": "Ввести мой id"}))
|
||||
await callback.message.delete()
|
||||
|
||||
|
||||
@orders_router.callback_query(OrderForm.worker_id)
|
||||
async def get_order_counterparty(message: Message | CallbackQuery, state: FSMContext):
|
||||
if isinstance(message, Message):
|
||||
worker_id = int(message.text)
|
||||
msg = message
|
||||
else:
|
||||
worker_id = int(message.data)
|
||||
msg = message.message
|
||||
await msg.answer("Введите данные заказчика ")
|
||||
await msg.delete()
|
||||
await state.update_data(worker_id=worker_id)
|
||||
await state.set_state(OrderForm.customer)
|
||||
|
||||
|
||||
# @orders_router.callback_query(OrderForm.counterparty)
|
||||
# async def get_order_customer(message: Message, state: FSMContext):
|
||||
#
|
||||
|
||||
@orders_router.message(OrderForm.customer)
|
||||
async def create_order(message: Message, state: FSMContext):
|
||||
await message.answer("Введите описание заказа в виде ключевых слов (АВР, ПСС, НКУ и т.д.) )")
|
||||
await state.update_data(customer=message.text)
|
||||
await state.set_state(OrderForm.description)
|
||||
|
||||
|
||||
@orders_router.message(OrderForm.description)
|
||||
async def create_order(message: Message, state: FSMContext):
|
||||
await state.update_data(description=message.text)
|
||||
order_ = await state.get_data()
|
||||
async with async_session_() as session:
|
||||
async with session.begin():
|
||||
session.add(Order(**order_))
|
||||
|
||||
await state.clear()
|
||||
await message.answer("Заказ успешно создан ")
|
||||
|
||||
|
||||
@orders_router.callback_query(lambda x: x.data == "find_orders")
|
||||
async def find_orders_menu(callback: CallbackQuery, state: FSMContext):
|
||||
await callback.message.edit_text(text="Выберите параметры поиска:")
|
||||
await callback.message.edit_reply_markup(reply_markup=create_inline_kb(width=1, **find_order_params))
|
||||
await state.set_state(SearchForm.search_option)
|
||||
|
||||
|
||||
@orders_router.callback_query(SearchForm.search_option)
|
||||
async def search_by_item(callback: CallbackQuery, state: FSMContext):
|
||||
search_option = re.search(r"^search_by_(\w+)", callback.data).group(1)
|
||||
await state.update_data(search_option=search_option)
|
||||
await state.set_state(SearchForm.data_to_search)
|
||||
await callback.message.answer(text="🔍 Введите данные для поиска")
|
||||
await callback.message.delete()
|
||||
|
||||
|
||||
@orders_router.message(SearchForm.data_to_search)
|
||||
async def search_by_item(message: Message, state: FSMContext):
|
||||
async with async_session_() as local_session:
|
||||
search_opt = await state.get_value("search_option")
|
||||
col = getattr(Order, search_opt)
|
||||
await message.answer(message.text)
|
||||
result = await local_session.execute(
|
||||
select(Order).where(col.ilike(f"%{message.text}%") if search_opt != "id" else col == int(message.text)))
|
||||
selected_orders = result.scalars().all()
|
||||
if selected_orders:
|
||||
await message.answer(text="Список заказов", reply_markup=create_inline_kb(width=1, **dict(
|
||||
(f"show_order_{order.id}", order.description or "Отсутствует") for order in selected_orders)))
|
||||
await state.update_data(search_result=selected_orders)
|
||||
await state.set_state(SearchForm.search_result)
|
||||
else:
|
||||
await message.answer(text="Заказов по вашему запросу не найдено")
|
||||
await state.clear()
|
||||
|
||||
|
||||
@orders_router.callback_query(SearchForm.search_result)
|
||||
async def show_order(callback: CallbackQuery, state: FSMContext):
|
||||
order_id = int(re.search(r"(\d+)", callback.data).group())
|
||||
|
||||
try:
|
||||
async with async_session_() as local_session:
|
||||
result = await local_session.execute(select(Order).filter(Order.id == order_id))
|
||||
order = result.scalars().first()
|
||||
|
||||
except Exception as err:
|
||||
logger.warning(err)
|
||||
|
||||
order_operation_upd = order_operation_update if await IsAdmin()(callback) else {}
|
||||
|
||||
if order:
|
||||
await callback.message.answer(text=f"Номер заказа: {order.id}\n"
|
||||
f"Заказчик: {order.customer}\n"
|
||||
f"Статус: {order.status_id}\n"
|
||||
f"Дата начала работ: {order.commencement_work}\n"
|
||||
f"Дата отгрузки: {order.end_work}\n"
|
||||
f"Дата создания: {order.created_at}\n"
|
||||
f"Описание: {order.description}",
|
||||
reply_markup=create_inline_kb(width=2, **dict(
|
||||
(f"{clbk}_{order.id}", text) for clbk, text in order_operation_base.items()))
|
||||
)
|
||||
|
||||
await callback.message.delete()
|
||||
await state.clear()
|
||||
|
||||
|
||||
@orders_router.callback_query(lambda x: x.data.startswith("get_order_photo"))
|
||||
async def send_order_photos(callback: CallbackQuery, bot: Bot):
|
||||
order_id = callback.data.split("_")[-1]
|
||||
media_item: Path
|
||||
media_group = []
|
||||
os.makedirs(Path(f"./photos/{order_id}/"), exist_ok=True)
|
||||
media_path = Path(f"./photos/{order_id}/").iterdir()
|
||||
if not (media_item := next(media_path, None)):
|
||||
text = f"Фото по заказу \"{order_id}\" отсутствуют "
|
||||
else:
|
||||
text = (f"Заказ: №{order_id}\n"
|
||||
f"")
|
||||
await bot.send_message(chat_id=callback.from_user.id, text=text)
|
||||
while media_item or media_group:
|
||||
if len(media_group) == 10 or (media_group and not media_item):
|
||||
await bot.send_media_group(chat_id=callback.from_user.id, media=media_group)
|
||||
media_group.clear()
|
||||
if media_item:
|
||||
try:
|
||||
input_type = InputMediaPhoto if media_item.suffix == ".jpg" else InputMediaVideo
|
||||
media_group.append(input_type(media=FSInputFile(media_item)))
|
||||
except Exception as err:
|
||||
logger.error(f"Ошибка при обработке {media_path}: {err}")
|
||||
|
||||
media_item = next(media_path, None)
|
||||
await asyncio.sleep(600)
|
||||
await callback.message.delete()
|
||||
|
||||
|
||||
@orders_router.callback_query(lambda x: x.data.startswith("add_order_photo"))
|
||||
async def reply_for_photo(callback: CallbackQuery, bot: Bot):
|
||||
await bot.send_message(text=f"Заказ: {callback.data.split("_")[-1]}\n"
|
||||
"Прикрепите файлы ответив на это сообщение сдвинув его влево, либо выберите 'ответить'",
|
||||
chat_id=callback.from_user.id)
|
||||
await callback.message.delete()
|
||||
|
||||
|
||||
@orders_router.message(
|
||||
F.reply_to_message)
|
||||
async def add_order_photo(message: Message, bot: Bot):
|
||||
order_id = re.search(r"(\d+)", message.reply_to_message.text).group()
|
||||
order_photos_path = f"/app/photos/{order_id}/"
|
||||
os.makedirs(order_photos_path, exist_ok=True)
|
||||
|
||||
item = message.video or message.photo[-1]
|
||||
file_extension = "jpg" if message.photo else "mp4"
|
||||
try:
|
||||
file = await bot.get_file(item.file_id)
|
||||
|
||||
await bot.download_file(file.file_path, destination=f"{order_photos_path}/{file.file_id}.{file_extension}",
|
||||
timeout=3600)
|
||||
await message.answer(f"Медиа файл {file.file_id[:10]} успешно прикреплен к заказу №{order_id}")
|
||||
|
||||
except AiogramError as err:
|
||||
logger.error(err)
|
||||
|
||||
await message.delete()
|
||||
try:
|
||||
await bot.delete_message(chat_id=message.chat.id, message_id=message.reply_to_message.message_id)
|
||||
except:
|
||||
pass
|
50
app/handlers/registration.py
Normal file
50
app/handlers/registration.py
Normal file
@@ -0,0 +1,50 @@
|
||||
import os
|
||||
from asyncio import Event
|
||||
|
||||
from aiogram import Router, Bot
|
||||
from aiogram.filters import CommandStart
|
||||
from aiogram.types import Message, User
|
||||
|
||||
from sqlalchemy import insert, select
|
||||
from sqlalchemy.orm import selectinload
|
||||
|
||||
from keyboards import create_inline_kb
|
||||
from database import async_session_, Worker
|
||||
|
||||
registration_router = Router()
|
||||
|
||||
registration_confirm: dict[int, Event] = {}
|
||||
user_info_template = ("Новый пользователь ждет регистрации:\n"
|
||||
"Имя: {}\n"
|
||||
"Фамилия: {}\n"
|
||||
"Юзернейм: @{}\n"
|
||||
"ID: @msg_{}\n")
|
||||
|
||||
|
||||
@registration_router.message(CommandStart())
|
||||
async def registration_command(message: Message, bot: Bot):
|
||||
admins_ids = os.getenv("BOT_ADMINS").split(",")
|
||||
async with async_session_() as session:
|
||||
async with session.begin():
|
||||
result = await session.execute(select(Worker).where(Worker.telegram_id == message.from_user.id))
|
||||
user = result.scalars().first()
|
||||
if not user:
|
||||
|
||||
user = message.from_user
|
||||
dict_for_inline = {f'reg @{user.id}': 'Allow', f'del @{user.id}': 'Reject'}
|
||||
user_info = user_info_template.format(user.first_name, user.last_name if user.last_name else 'Не указана',
|
||||
user.username if user.username else 'Не указан', user.id)
|
||||
for admin in admins_ids:
|
||||
await bot.send_message(chat_id=admin, text=user_info)
|
||||
await bot.send_message(chat_id=admin, text='Зарегистрировать пользователя',
|
||||
reply_markup=create_inline_kb(width=2, **dict_for_inline))
|
||||
reg_confirm = Event()
|
||||
registration_confirm[user.id] = reg_confirm
|
||||
if await reg_confirm:
|
||||
async with async_session_() as local_session:
|
||||
async with local_session.begin():
|
||||
local_session.add(Worker(telegram_id=int(user.id), name=user.first_name))
|
||||
del registration_confirm[user.id]
|
||||
|
||||
else:
|
||||
await message.answer("Работа бота возобновлена")
|
5
app/keyboards/__init__.py
Normal file
5
app/keyboards/__init__.py
Normal file
@@ -0,0 +1,5 @@
|
||||
from .inline import create_inline_kb, button_create
|
||||
from .menu_commands import set_main_menu,commands
|
||||
|
||||
|
||||
__all__ = ["create_inline_kb", "button_create", "set_main_menu","commands"]
|
35
app/keyboards/inline.py
Normal file
35
app/keyboards/inline.py
Normal file
@@ -0,0 +1,35 @@
|
||||
from aiogram.types import InlineKeyboardButton, InlineKeyboardMarkup, KeyboardButton
|
||||
from aiogram.utils.keyboard import InlineKeyboardBuilder, ReplyKeyboardBuilder
|
||||
|
||||
|
||||
def button_create(iterable: iter, width: int = 1):
|
||||
""" Функуия для преобразования списка в кнопки для клавиатуры """
|
||||
kb_builder = ReplyKeyboardBuilder()
|
||||
kb_builder.row(*[KeyboardButton(text=f'{iterable[i]}') for i in range(len(iterable))], width=width)
|
||||
return kb_builder.as_markup(resize_keyboard=True, one_time_keyboard=True)
|
||||
|
||||
|
||||
def create_inline_kb(width: int = 1,
|
||||
*args: str, **kwargs: str) -> InlineKeyboardMarkup:
|
||||
# Инициализируем билдер
|
||||
kb_builder = InlineKeyboardBuilder()
|
||||
# Инициализируем список для кнопок
|
||||
buttons: list[InlineKeyboardButton] = []
|
||||
|
||||
# Заполняем список кнопками из аргументов args и kwargs
|
||||
if args:
|
||||
for button in args:
|
||||
buttons.append(InlineKeyboardButton(
|
||||
text=button,
|
||||
callback_data=button))
|
||||
if kwargs:
|
||||
for button, text in kwargs.items():
|
||||
buttons.append(InlineKeyboardButton(
|
||||
text=text,
|
||||
callback_data=button))
|
||||
|
||||
# Распаковываем список с кнопками в билдер методом row c параметром width
|
||||
kb_builder.row(*buttons, width=width)
|
||||
|
||||
# Возвращаем объект инлайн-клавиатуры
|
||||
return kb_builder.as_markup(resize_keyboard=True, one_time_keyboard=True)
|
10
app/keyboards/menu_commands.py
Normal file
10
app/keyboards/menu_commands.py
Normal file
@@ -0,0 +1,10 @@
|
||||
from aiogram import Bot
|
||||
from aiogram.types import BotCommand
|
||||
|
||||
commands = {'/help': 'Помощь по работе с ботом', '/support': 'Получить контакты техподдержки',
|
||||
'/orders': 'Заказы', '/components': 'Товары', '/start': 'Запуск/перезапуск бота'}
|
||||
|
||||
|
||||
async def set_main_menu(bot: Bot):
|
||||
await bot.set_my_commands([
|
||||
BotCommand(command=command, description=description) for command, description in commands.items()])
|
27
app/main.py
Normal file
27
app/main.py
Normal file
@@ -0,0 +1,27 @@
|
||||
import os
|
||||
import asyncio
|
||||
from dotenv import load_dotenv
|
||||
from aiogram import Dispatcher, Bot
|
||||
from handlers import *
|
||||
from keyboards import set_main_menu
|
||||
from middlewares import AccessCheckMiddleware
|
||||
|
||||
load_dotenv(".env")
|
||||
bot = Bot(token=os.getenv("TOKEN"))
|
||||
|
||||
|
||||
async def main() -> None:
|
||||
dp = Dispatcher()
|
||||
|
||||
dp.startup.register(set_main_menu)
|
||||
dp.include_router(registration_router)
|
||||
dp.include_router(admin_router)
|
||||
dp.update.outer_middleware(AccessCheckMiddleware())
|
||||
dp.include_router(orders_router)
|
||||
dp.include_router(components_router)
|
||||
|
||||
await bot.delete_webhook(drop_pending_updates=True)
|
||||
await dp.start_polling(bot)
|
||||
|
||||
|
||||
asyncio.run(main())
|
4
app/middlewares/__init__.py
Normal file
4
app/middlewares/__init__.py
Normal file
@@ -0,0 +1,4 @@
|
||||
from .outer_middlewares import AccessCheckMiddleware
|
||||
|
||||
|
||||
__all__ = ["AccessCheckMiddleware"]
|
29
app/middlewares/outer_middlewares.py
Normal file
29
app/middlewares/outer_middlewares.py
Normal file
@@ -0,0 +1,29 @@
|
||||
import logging
|
||||
from typing import Any, Awaitable, Callable, Dict
|
||||
from aiogram import BaseMiddleware, Bot
|
||||
from aiogram.types import TelegramObject
|
||||
from database import async_session_, Worker
|
||||
from sqlalchemy import select
|
||||
|
||||
|
||||
|
||||
class AccessCheckMiddleware(BaseMiddleware):
|
||||
sessions_in_memory_db = set()
|
||||
async def __call__(
|
||||
self,
|
||||
handler: Callable[[TelegramObject, Dict[str, Any]], Awaitable[Any]],
|
||||
event: TelegramObject,
|
||||
data: Dict[str, Any]
|
||||
) -> Any:
|
||||
event_data = event.message or event.callback_query
|
||||
user = event_data.from_user.id
|
||||
if user not in self.sessions_in_memory_db:
|
||||
async with async_session_() as session:
|
||||
async with session.begin():
|
||||
result = await session.execute(select(Worker).where(Worker.telegram_id == event_data.from_user.id))
|
||||
user = result.scalars().first()
|
||||
if user:
|
||||
self.sessions_in_memory_db.add(event_data.from_user.id)
|
||||
return await handler(event, data)
|
||||
return None
|
||||
|
Reference in New Issue
Block a user