Initial commit

This commit is contained in:
Михаил Ломоносов 2024-07-17 23:35:45 +03:00
parent 7a1a025471
commit a7bfd5ed4a
42 changed files with 2041 additions and 2 deletions

13
Dockerfile Normal file
View File

@ -0,0 +1,13 @@
FROM python:3.11
WORKDIR /moderaotrbot
COPY ./ ./
RUN rm -rf /etc/localtime
RUN ln -s /usr/share/zoneinfo/Europe/Moscow /etc/localtime
RUN echo "Europe/Moscow" > /etc/timezone
RUN pip install --upgrade pip
RUN pip install --no-cache-dir -r requirements.txt
CMD ["python","-u", "main.py"]

View File

@ -1,3 +1,35 @@
# chat_moderator_bot
## Бот транскрибатор
Удаляет сообщения из группы с бан-вордами. После, скидывает изменяемое в админ-панели сообщение
### Конфигурация .env
```
BOT_TOKEN - Токен бота
ADMINS - Админы, для которых работает /start. Записывать через запятую: user_id1,user_id2,user_id3 и т.д.
POSTGRES_NAME - Имя postgres бд
POSTGRES_HOST - Хост postgres бд
POSTGRES_PORT - Порт postgres бд
POSTGRES_PASSWORD - Пароль от postgres бд
POSTGRES_USER - Пользовтаель postgres бд
REDIS_NAME - Имя redis бд
REDIS_HOST - Хост redis бд
REDIS_PORT - Порт redis бд
REDIS_PASSWORD - Пароль redis бд
```
## Шаг 1: Получение Токена бота
1. Перейдите в [BotFather](https://t.me/BotFather)
2. Скопируйте Токен вашего телеграмм бота
## Шаг 2: Загрузка переменных в окружение
1. Создайте файл `.env` в корневой папке проекта и заполните его значениями выше.
2. Скачайте необходимые проекту библиотеки командой:
```
pip install -r requirements.txt
```

27
config.py Normal file
View File

@ -0,0 +1,27 @@
from os import getenv
from aiogram.types import BotCommand
from dotenv import load_dotenv
load_dotenv()
BOT_TOKEN: str = getenv('BOT_TOKEN')
ADMINS: list = [int(admin) for admin in getenv('ADMINS').split(',')]
POSTGRES_NAME: str = getenv('POSTGRES_NAME')
POSTGRES_HOST: str = getenv('POSTGRES_HOST')
POSTGRES_PORT: int = int(getenv('POSTGRES_PORT'))
POSTGRES_PASSWORD: str = getenv('POSTGRES_PASSWORD')
POSTGRES_USER: str = getenv('POSTGRES_USER')
REDIS_NAME: int = int(getenv('REDIS_NAME'))
REDIS_HOST: str = getenv('REDIS_HOST')
REDIS_PORT: int =int(getenv('REDIS_PORT'))
REDIS_PASSWORD: str = getenv('REDIS_PASSWORD')
commands = [
BotCommand(
command='start',
description='Меню'
)
]

8
core.py Normal file
View File

@ -0,0 +1,8 @@
from aiogram import Bot
from config import BOT_TOKEN
bot = Bot(
token=BOT_TOKEN,
parse_mode="HTML"
)

BIN
data/ban_words.xlsx Normal file

Binary file not shown.

33
docker-compose.yml Normal file
View File

@ -0,0 +1,33 @@
services:
bot:
build:
context: .
restart: always
depends_on:
- db
env_file:
- .env
db:
image: postgres
restart: always
environment:
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
- POSTGRES_DB=${POSTGRES_NAME}
expose:
- '5432'
volumes:
- postgres_data:/var/lib/postgresql/data
redis:
image: redis:latest
restart: always
command: [ "redis-server", "--requirepass", "4CEqaD0JL8gTM4XWVt8K" ]
expose:
- '6379'
volumes:
- redis_data:/data
volumes:
postgres_data:
redis_data:

8
handlers/__init__.py Normal file
View File

@ -0,0 +1,8 @@
from handlers.ban_words import router as rban_words
from handlers.commands import router as rcommands
from handlers.come_back import router as rcome_back
from handlers.message import router as rmessage
from handlers.group import router as rgroup
from handlers.ban_media import router as rban_media
routers = [rcommands, rban_words, rcome_back, rmessage, rgroup, rban_media]

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

66
handlers/ban_media.py Normal file
View File

@ -0,0 +1,66 @@
from aiogram import Router, F
from aiogram.fsm.context import FSMContext
from aiogram.types import CallbackQuery
from templates import commands as tcommands
from utils.db import Postgres, Redis
router = Router()
@router.callback_query(F.data.startswith('enable_ban_media_'))
async def enable_ban_media_btn(call: CallbackQuery, state: FSMContext):
"""
Ловит кнопку Включить
:param call: CallbackQuery
:param state: FSMContext
:return:
"""
state_data = await state.get_data()
p = Postgres()
r = Redis()
await p.update_data(
table_name='ban_media',
new_data={call.data[17:]: True},
query_filter={}
)
await r.update_dict(
key='ban_media',
value={call.data[17:]: 'yes'}
)
print('new redis data', {call.data[17:]: 'yes'})
await state_data['last_msg'].edit_text(
text=tcommands.start_text,
reply_markup=await tcommands.start_ikb()
)
@router.callback_query(F.data.startswith('disable_ban_media_'))
async def disable_ban_media_btn(call: CallbackQuery, state: FSMContext):
"""
Ловит кнопку Выключить
:param call: CallbackQuery
:param state: FSMContext
:return:
"""
state_data = await state.get_data()
p = Postgres()
r = Redis()
await p.update_data(
table_name='ban_media',
new_data={call.data[18:]: False},
query_filter={}
)
await r.update_dict(
key='ban_media',
value={call.data[18:]: ''}
)
print('new redis data', {call.data[18:]: ''})
await state_data['last_msg'].edit_text(
text=tcommands.start_text,
reply_markup=await tcommands.start_ikb()
)

134
handlers/ban_words.py Normal file
View File

@ -0,0 +1,134 @@
import logging
from aiogram import Router, F
from aiogram.fsm.context import FSMContext
from aiogram.types import CallbackQuery, FSInputFile, Message
from templates import ban_words as tban_words
from templates import commands as tcommands
from utils.defs import delete_msg, create_xlsx
from utils.db import Redis, Postgres
router = Router()
@router.callback_query(F.data == 'ban_words')
async def ban_words_btn(call: CallbackQuery, state: FSMContext):
"""
Ловит кнопку 🚫 Стоп слова
:param call: CallbackQuery
:param state: FSMContext
:return:
"""
state_data = await state.get_data()
document_path = await create_xlsx()
last_msg = await call.message.answer_document(
document=FSInputFile(document_path),
reply_markup=tban_words.actions_ikb()
)
await delete_msg(
msg=state_data.get('last_msg')
)
await state.update_data(
last_msg=last_msg
)
@router.callback_query(F.data.startswith('ban_words_action_'))
async def ban_words_action_btn(call: CallbackQuery, state: FSMContext):
"""
Ловит кнопки Добавить Удалить
:param call: CallbackQuery
:param state: FSMContext
:return:
"""
state_data = await state.get_data()
last_msg = await call.message.answer(
text=tban_words.send_words_text,
reply_markup=tban_words.send_words_ikb()
)
await state.update_data(
last_msg=last_msg,
ban_words_action=call.data[17:]
)
await state.set_state(
state=tban_words.SendState.words
)
await delete_msg(
msg=state_data.get('last_msg')
)
@router.message(tban_words.SendState.words, F.text)
async def get_words(msg: Message, state: FSMContext):
"""
Ловит новое сообщение
:param msg: Message
:param state: FSMContext
:return:
"""
state_data = await state.get_data()
words = msg.text.split('\n')
r = Redis()
p = Postgres()
redis_ban_words = await r.get_list(
key='ban_words'
)
if state_data['ban_words_action'] == 'add':
msg_text = tban_words.success_text.format(
action='добавлены'
)
for word in words:
await p.create_row(
table_name='ban_words',
data_to_insert={'word': word.lower()}
)
redis_ban_words.append(word.lower())
else:
msg_text = tban_words.success_text.format(
action='удалены'
)
not_deleted = []
for word in words:
try:
await p.query(
query_text=f"DELETE FROM ban_words WHERE LOWER(word)='{word.lower()}'"
)
redis_ban_words.remove(word.lower())
except Exception as e:
logging.error(f'get_words {e}')
not_deleted.append(word)
if not_deleted:
msg_text = tban_words.not_success_remove_text.format(
words='\n'.join(not_deleted)
)
await state_data['last_msg'].edit_text(
text=msg_text,
reply_markup=await tcommands.start_ikb()
)
await msg.delete()
await r.delete_key(
'ban_words'
)
await r.update_list(
'ban_words',
*redis_ban_words
)
await state.set_state(
state=None
)

115
handlers/come_back.py Normal file
View File

@ -0,0 +1,115 @@
from aiogram import Router, F
from aiogram.types import CallbackQuery
from aiogram.fsm.context import FSMContext
from handlers.ban_words import ban_words_btn
from templates import message as tmessage
from templates import commands as tcommands
from handlers.message import message_btn
from utils.defs import delete_msg
router = Router()
@router.callback_query(F.data == 'come_back_buttons')
async def come_back_buttons_btn(call: CallbackQuery, state: FSMContext):
"""
Ловит кнопку Назад
:param call: CallbackQuery
:param state: FSMContext
:return:
"""
state_data = await state.get_data()
await state_data['last_msg'].edit_text(
text=tmessage.send_buttons_text,
reply_markup=tmessage.send_buttons_ikb()
)
await delete_msg(
msg=state_data.get('preview_msg')
)
state_data['edited_message_data']['buttons'] = []
await state.update_data(
edited_message_data=state_data['edited_message_data']
)
await state.set_state(
state=tmessage.SendState.buttons
)
@router.callback_query(F.data == 'come_back_message')
async def come_back_message_btn(call: CallbackQuery, state: FSMContext):
"""
Ловит кнопку Назад
:param call: CallbackQuery
:param state: FSMContext
:return:
"""
state_data = await state.get_data()
await state_data['last_msg'].edit_text(
text=tmessage.send_message_text,
reply_markup=tmessage.send_message_ikb()
)
await state.set_state(
state=tmessage.SendState.message
)
@router.callback_query(F.data == 'come_back_preview')
async def come_back_preview_btn(call: CallbackQuery, state: FSMContext):
"""
Ловит кнопку Назад
:param call: CallbackQuery
:param state: FSMContext
:return:
"""
await message_btn(
call=call,
state=state
)
@router.callback_query(F.data == 'come_back_menu')
async def come_back_preview_btn(call: CallbackQuery, state: FSMContext):
"""
Ловит кнопку Назад
:param call: CallbackQuery
:param state: FSMContext
:return:
"""
state_data = await state.get_data()
last_msg = await call.message.answer(
text=tcommands.start_text,
reply_markup=await tcommands.start_ikb()
)
await state.update_data(
last_msg=last_msg
)
await delete_msg(
msg=state_data.get('preview_msg')
)
await delete_msg(
msg=state_data.get('last_msg')
)
@router.callback_query(F.data == 'come_back_ban_words')
async def come_back_ban_words_btn(call: CallbackQuery, state: FSMContext):
"""
Ловит кнопку Назад
:param call: CallbackQuery
:param state: FSMContext
:return:
"""
await ban_words_btn(
call=call,
state=state
)

39
handlers/commands.py Normal file
View File

@ -0,0 +1,39 @@
from aiogram import Router
from aiogram.types import Message, BotCommandScopeChat
from aiogram.filters import Command
from aiogram.fsm.context import FSMContext
from core import bot
from templates import commands as tcommands
from config import ADMINS, commands
router = Router()
@router.message(Command("start"))
async def start_command(msg: Message, state: FSMContext):
"""
Ловит команду /start
:param msg: Message
:param state: FSMContext
:return:
"""
await msg.delete()
if not msg.from_user.id in ADMINS:
return
last_msg = await msg.answer(
text=tcommands.start_text,
reply_markup=await tcommands.start_ikb()
)
await state.update_data(
last_msg=last_msg
)
await bot.set_my_commands(
commands=commands,
scope=BotCommandScopeChat(
chat_id=msg.from_user.id
)
)

92
handlers/group.py Normal file
View File

@ -0,0 +1,92 @@
from aiogram import Router, F
from aiogram.fsm.context import FSMContext
from aiogram.types import Message
from templates.message import send_preview
from utils.db import Redis, Postgres
from utils.defs import delete_msg
router = Router()
@router.message(F.chat.func(lambda chat: chat.type in ('group', 'supergroup')))
async def get_all_messages(msg: Message, state: FSMContext):
"""
Ловит все сообщения в чате
:param msg: Message
:param state: FSMContext
:return:
"""
ban = False
msg_text = msg.text
if not msg_text:
msg_text = msg.caption
r = Redis()
p = Postgres()
if msg_text:
ban_words = await r.get_list(
key='ban_words'
)
if not ban_words:
ban_words = await p.get_data(
table_name='ban_words'
)
ban_words = [word['word'] for word in ban_words]
await r.delete_key(
'ban_words'
)
await r.update_list(
'ban_words',
*ban_words
)
for ban_word in ban_words:
if ban_word in msg_text.lower():
ban = True
else:
ban_media = await r.get_dict(
key='ban_media'
)
print(ban_media)
if not ban_media.get('video') or ban_media.get('photo'):
postgres_ban_media = await p.get_data(
table_name='ban_media'
)
ban_media = {}
for key, value in postgres_ban_media[0].items():
ban_media[key] = 'yes' if value else ''
await r.update_dict(
'ban_media',
value=ban_media
)
if ban_media.get('video') and msg.video:
ban = True
if ban_media.get('photo') and msg.photo:
ban = True
if ban:
await delete_msg(
msg=msg
)
message_data = await p.get_data(
table_name='message'
)
if msg.from_user.username:
username = f'@{msg.from_user.username}'
else:
username = msg.from_user.full_name
if message_data[0]['included']:
await send_preview(
chat_id=msg.chat.id,
message_data=message_data[0],
username=username
)

252
handlers/message.py Normal file
View File

@ -0,0 +1,252 @@
import json
from aiogram import Router, F
from aiogram.fsm.context import FSMContext
from aiogram.types import CallbackQuery, Message
from templates import commands as tcommands
from templates import message as tmessage
from utils.db import Postgres
from utils.defs import delete_msg
router = Router()
@router.callback_query(F.data == 'message')
async def message_btn(call: CallbackQuery, state: FSMContext):
"""
Ловит кнопку 💬 Стоп сообщение
:param call: CallbackQuery
:param state: FSMContext
:return:
"""
state_data = await state.get_data()
p = Postgres()
message_data = await p.get_data(
table_name='message'
)
preview_msg = await tmessage.send_preview(
message_data=message_data[0],
chat_id=call.from_user.id
)
last_msg = await call.message.answer(
text=tmessage.actions_text.format(
include='Да' if message_data[0]['included'] else 'Нет'
),
reply_markup=tmessage.actions_ikb(
included=message_data[0]['included']
)
)
await delete_msg(
msg=state_data.get('last_msg')
)
await state.update_data(
preview_msg=preview_msg,
last_msg=last_msg
)
@router.callback_query(F.data == 'edit_message')
async def edit_message_btn(call: CallbackQuery, state: FSMContext):
"""
Ловит кнопку 📝 Редактировать
:param call: CallbackQuery
:param state: FSMContext
:return:
"""
state_data = await state.get_data()
await state_data['last_msg'].edit_text(
text=tmessage.send_message_text,
reply_markup=tmessage.send_message_ikb()
)
await state.set_state(
state=tmessage.SendState.message
)
await delete_msg(
msg=state_data.get('preview_msg')
)
await state.update_data(
edited_message_data={
'text': '',
'buttons': [],
'media': ''
}
)
@router.message(tmessage.SendState.message, F.content_type.in_({'text', 'photo'}))
async def get_edit_message(msg: Message, state: FSMContext):
"""
Ловит новое сообщение
:param msg: Message
:param state: FSMContext
:return:
"""
state_data = await state.get_data()
await state_data['last_msg'].edit_text(
text=tmessage.send_buttons_text,
reply_markup=tmessage.send_buttons_ikb()
)
await msg.delete()
await state.set_state(
state=tmessage.SendState.buttons
)
if msg.photo:
state_data['edited_message_data']['text'] = msg.caption
state_data['edited_message_data']['media'] = msg.photo[-1].file_id
else:
state_data['edited_message_data']['text'] = msg.text
state_data['edited_message_data']['media'] = None
await state.update_data(
edited_message_data=state_data['edited_message_data']
)
@router.message(tmessage.SendState.buttons, F.text)
async def get_edit_buttons(msg: Message, state: FSMContext):
"""
Ловит новое url кнопки к сообщению
:param msg: Message
:param state: FSMContext
:return:
"""
state_data = await state.get_data()
buttons = tmessage.build_url_ikb(msg.text)
await msg.delete()
if not buttons:
await state_data['last_msg'].edit_text(
text=tmessage.incorrect_data_text + '\n\n' + tmessage.send_buttons_text,
reply_markup=tmessage.send_buttons_ikb()
)
return
await delete_msg(
msg=state_data.get('last_msg')
)
state_data['edited_message_data']['buttons'] = buttons
preview_msg = await tmessage.send_preview(
message_data=state_data['edited_message_data'],
chat_id=msg.from_user.id
)
last_msg = await msg.answer(
text=tmessage.check_data_text,
reply_markup=tmessage.check_data_ikb()
)
await state.update_data(
edited_message_data=state_data['edited_message_data'],
preview_msg=preview_msg,
last_msg=last_msg
)
await state.set_state(
state=None
)
@router.callback_query(F.data == 'pass_buttons')
async def pass_buttons_btn(call: CallbackQuery, state: FSMContext):
"""
Ловит кнопку Пропустить
:param call: CallbackQuery
:param state: FSMContext
:return:
"""
state_data = await state.get_data()
await delete_msg(
msg=state_data.get('last_msg')
)
preview_msg = await tmessage.send_preview(
message_data=state_data['edited_message_data'],
chat_id=call.from_user.id
)
last_msg = await call.message.answer(
text=tmessage.check_data_text,
reply_markup=tmessage.check_data_ikb()
)
await state.update_data(
preview_msg=preview_msg,
last_msg=last_msg
)
await state.set_state(
state=None
)
@router.callback_query(F.data == 'publish_message')
async def publish_message_btn(call: CallbackQuery, state: FSMContext):
"""
Ловит кнопку Опубликовать
:param call: CallbackQuery
:param state: FSMContext
:return:
"""
state_data = await state.get_data()
state_data['edited_message_data']['buttons'] = json.dumps(
state_data['edited_message_data']['buttons']
)
p = Postgres()
await p.update_data(
table_name='message',
new_data=state_data['edited_message_data'],
query_filter={}
)
await state_data['last_msg'].edit_text(
text=tmessage.publish_message_text,
reply_markup=await tcommands.start_ikb()
)
await delete_msg(
msg=state_data.get('preview_msg')
)
@router.callback_query(F.data.startswith('included_message_'))
async def included_message_btn(call: CallbackQuery, state: FSMContext):
"""
Ловит кнопку 📝 Редактировать
:param call: CallbackQuery
:param state: FSMContext
:return:
"""
state_data = await state.get_data()
if call.data[17:] == 'true':
included = True
else:
included = False
await state_data['last_msg'].edit_text(
text=tmessage.actions_text.format(
include='Да' if included else 'Нет'
),
reply_markup=tmessage.actions_ikb(
included=included
)
)
p = Postgres()
await p.update_data(
table_name='message',
new_data={'included': included},
query_filter={}
)

36
main.py Normal file
View File

@ -0,0 +1,36 @@
import asyncio
import logging
import sys
from aiogram import Dispatcher
from core import bot
from handlers import routers
from utils.db import Postgres
# from utils.middleware import DeleteMessage
dp = Dispatcher()
dp.include_routers(*routers)
# dp.update.middleware.register(
# middleware=DeleteMessage()
# )
async def start():
"""
Запускает бота
:return:
"""
p = Postgres()
await p.create_tables()
await bot.delete_webhook()
await dp.start_polling(bot)
if __name__ == "__main__":
logging.basicConfig(
level=logging.INFO,
stream=sys.stdout
)
asyncio.run(start())

7
requirements.txt Normal file
View File

@ -0,0 +1,7 @@
aiogram==3.6.0
aiohttp==3.9.5
python-dotenv==1.0.1
openpyxl~=3.1.5
pandas~=2.2.2
redis~=5.0.7
asyncpg~=0.29.0

0
templates/__init__.py Normal file
View File

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

64
templates/ban_words.py Normal file
View File

@ -0,0 +1,64 @@
from aiogram.types import InlineKeyboardButton, InlineKeyboardMarkup
from aiogram.utils.keyboard import InlineKeyboardBuilder
from aiogram.fsm.state import StatesGroup, State
class SendState(StatesGroup):
words = State()
send_words_text = """
Отправь список слов по одному или столбцом
"""
success_text = """
Слова {action}!
"""
not_success_remove_text = """
Что-то пошло не так. Не удалил слова:
{words}
"""
def send_words_ikb() -> InlineKeyboardMarkup:
"""
- Назад
:return: объект клавиатуры для параметра reply_markup
"""
builder = InlineKeyboardBuilder()
builder.add(
InlineKeyboardButton(
text='⬅️ Назад',
callback_data='come_back_ban_words'
)
)
builder.adjust(1)
return builder.as_markup()
def actions_ikb() -> InlineKeyboardMarkup:
"""
- Добавить
- Удалить
- Назад
:return: объект клавиатуры для параметра reply_markup
"""
builder = InlineKeyboardBuilder()
builder.add(
InlineKeyboardButton(
text=' Добавить',
callback_data='ban_words_action_add'
),
InlineKeyboardButton(
text=' Удалить',
callback_data='ban_words_action_remove'
),
InlineKeyboardButton(
text='⬅️ Назад',
callback_data='come_back_menu'
)
)
builder.adjust(1)
return builder.as_markup()

62
templates/commands.py Normal file
View File

@ -0,0 +1,62 @@
from aiogram.types import InlineKeyboardButton, InlineKeyboardMarkup
from aiogram.utils.keyboard import InlineKeyboardBuilder
from utils.db import Postgres
start_text = """
Привет, Админ!
"""
async def start_ikb() -> InlineKeyboardMarkup:
"""
-🚫 Стоп слова
-💬 Стоп сообщение
-Запретить / Разрешить видео без опис.
-Запретить / Разрешить фото без опис.
:return: объект клавиатуры для параметра reply_markup
"""
builder = InlineKeyboardBuilder()
ban_media_photo = {'text': '', 'callback_data': ''}
ban_media_video = {'text': '', 'callback_data': ''}
ban_media = await Postgres().get_data(
table_name='ban_media'
)
if not ban_media[0]['photo']:
ban_media_photo['text'] = 'Запретить фото без описания'
ban_media_photo['callback_data'] = 'enable_ban_media_photo'
else:
ban_media_photo['text'] = 'Разрешить фото без описания'
ban_media_photo['callback_data'] = 'disable_ban_media_photo'
if not ban_media[0]['video']:
ban_media_video['text'] = 'Запретить видео без описания'
ban_media_video['callback_data'] = 'enable_ban_media_video'
else:
ban_media_video['text'] = 'Разрешить видео без описания'
ban_media_video['callback_data'] = 'disable_ban_media_video'
builder.add(
InlineKeyboardButton(
text='🚫 Стоп слова',
callback_data='ban_words'
),
InlineKeyboardButton(
text='💬 Стоп сообщение',
callback_data='message'
),
InlineKeyboardButton(
text=ban_media_video['text'],
callback_data=ban_media_video['callback_data']
),
InlineKeyboardButton(
text=ban_media_photo['text'],
callback_data=ban_media_photo['callback_data']
)
)
builder.adjust(1)
return builder.as_markup()

226
templates/message.py Normal file
View File

@ -0,0 +1,226 @@
import json
from aiogram.fsm.state import StatesGroup, State
from aiogram.types import InlineKeyboardButton, InlineKeyboardMarkup
from aiogram.types import Message
from aiogram.utils.keyboard import InlineKeyboardBuilder
from core import bot
class SendState(StatesGroup):
message = State()
buttons = State()
actions_text = """
Сообщение включено: <b>{include}</b>
Выберите действие:
"""
send_message_text = """
Отправьте сообщение с прикреплением до одного фото:
"""
send_buttons_text = """
Отправьте кнопки в таком формате:
<b>Кнопка в первом ряду - http://example.com</b>
<b>Кнопка во втором ряду - http://example.com</b>
Используйте разделитель " | ", чтобы добавить до 8 кнопок в один ряд (допустимо 6 рядов):
<b>Кнопка в ряду - http://example.com | Другая кнопка в ряду - http://example.com</b>
"""
incorrect_data_text = """
<b>Не верный формат данных</b>
"""
check_data_text = """
Проверьте введённые данные
"""
publish_message_text = """
Обуликовано успешно
"""
def check_data_ikb() -> InlineKeyboardMarkup:
"""
- Опубликовать
- Назад
:return: объект клавиатуры для параметра reply_markup
"""
builder = InlineKeyboardBuilder()
builder.add(
InlineKeyboardButton(
text='✅ Опубликовать',
callback_data='publish_message'
),
InlineKeyboardButton(
text='⬅️ Назад',
callback_data='come_back_buttons'
)
)
builder.adjust(1)
return builder.as_markup()
async def send_preview(message_data: dict, chat_id: int, username='') -> Message | None:
"""
Присылает превью сообщения
:param message_data: Данные сообщения из бд
:param chat_id: ID телеграм чата куда надо прислать превью
:param username: Username пользователя
:return:
"""
msg_text = message_data['text']
if username:
msg_text = f'{username}\n\n' + message_data['text']
if message_data['media']:
preview_msg = await bot.send_photo(
chat_id=chat_id,
caption=msg_text,
photo=message_data['media'],
reply_markup=url_ikb(
row_buttons=message_data['buttons']
)
)
else:
preview_msg = await bot.send_message(
chat_id=chat_id,
text=msg_text,
reply_markup=url_ikb(
row_buttons=message_data['buttons']
)
)
return preview_msg
def send_buttons_ikb() -> InlineKeyboardMarkup:
"""
- Пропустить
- Назад
:return: объект клавиатуры для параметра reply_markup
"""
builder = InlineKeyboardBuilder()
builder.add(
InlineKeyboardButton(
text='➡️ Пропустить',
callback_data='pass_buttons'
),
InlineKeyboardButton(
text='⬅️ Назад',
callback_data='come_back_message'
)
)
builder.adjust(1)
return builder.as_markup()
def send_message_ikb() -> InlineKeyboardMarkup:
"""
- Назад
:return: объект клавиатуры для параметра reply_markup
"""
builder = InlineKeyboardBuilder()
builder.add(
InlineKeyboardButton(
text='⬅️ Назад',
callback_data='come_back_preview'
)
)
builder.adjust(1)
return builder.as_markup()
def actions_ikb(included: bool) -> InlineKeyboardMarkup:
"""
-Вкл \ Выкл
-📝 Редактировать
- Назад
:param included: bool включено ли сообщение или нет
:return: объект клавиатуры для параметра reply_markup
"""
builder = InlineKeyboardBuilder()
if included:
included_btn = InlineKeyboardButton(
text='Выкл ❌',
callback_data='included_message_false'
)
else:
included_btn = InlineKeyboardButton(
text='Вкл ✅',
callback_data='included_message_true'
)
builder.add(
included_btn,
InlineKeyboardButton(
text='📝 Редактировать',
callback_data='edit_message'
),
InlineKeyboardButton(
text='⬅️ Назад',
callback_data='come_back_menu'
)
)
builder.adjust(1)
return builder.as_markup()
def build_url_ikb(msg_text: str) -> list:
"""
Создаёт клавиатуру с url кнопками пользователя
:param msg_text: Текст сообщения с заданными параметры клавиатуру
:return: list с кнопками клавиатуры
"""
try:
paragraphs = msg_text.split('\n')
row_buttons = []
for paragraph in paragraphs:
row = []
for button_text_data in paragraph.split(' | '):
row.append(button_text_data.strip().split(' - '))
row_buttons.append(row)
return row_buttons
except:
return []
def url_ikb(row_buttons: list | str) -> InlineKeyboardMarkup | None:
"""
Создаёт клавиатуру с url кнопками пользователя
:param row_buttons: List с кнопками клавиатуры
:return: В случае ошибки False, если норм то объект клавиатуры для параметра reply_markup
"""
try:
if isinstance(row_buttons, str):
row_buttons = json.loads(row_buttons)
builder = InlineKeyboardBuilder()
rows_len = [0, 0, 0, 0, 0, 0]
i = 0
for row in row_buttons:
rows_len[i] = len(row)
for button in row:
builder.add(
InlineKeyboardButton(
text=button[0],
url=button[1]
)
)
i += 1
builder.adjust(*rows_len)
if row_buttons:
return builder.as_markup()
return None
except:
return None

458
templates/moderation.py Normal file
View File

@ -0,0 +1,458 @@
import datetime
import logging
from aiogram.fsm.state import StatesGroup, State
from aiogram.types import InlineKeyboardButton, InlineKeyboardMarkup, FSInputFile
from aiogram.utils.keyboard import InlineKeyboardBuilder
from config import FILES_PATH, ROLE_MODERATOR_ID, ROLE_CURATOR_ID
from core import bot
from utils.bitrix import notify
from utils.db import get_data, query
class ModerationState(StatesGroup):
confirm = State()
confirm_confirm = State()
revision = State()
grade = State()
reject = State()
check_revision_comment = State()
check_reject_comment = State()
date = State()
new_idea_text = """
{name}, пожалуйста, ознакомьтесь с новой НеоИдеей.
"""
new_idea_data_text = """
<b>Автор:</b> {author_name}
<b>Дата:</b> {creation_date}
<b>Подразделение:</b> {department}
<b>Категория:</b> {category}
<b>Город:</b> {city}
<b>Название:</b> «{idea_name}»
<b>Содержание:</b> {idea_content}
"""
choose_action_text = """
Пожалуйста, выберите одно из следующих действий:
"""
confirm_action_text = """
<b>Вы согласовываете НеоИдею:</b>
«{idea_name}».
<b>Автор:</b> {author_name}
"""
choose_responsible_text = """
Пожалуйста, выберите <b>Ответственного за категорию</b> для передачи НеоИдеи в работу.
"""
send_date_text = """
В календаре укажите крайние сроки выполнения:
"""
confirm_responsible_date_text = """
Вы передаёте НеоИдею в работу.
<b>Название:</b> «{idea_name}».
<b>Автор:</b> {author_name}
<b>Ответственный за категорию:</b> {responsible_name}
<b>Крайние сроки выполнения:</b> {date}
"""
confirm_action_send_text = """
Спасибо! Вы передали НеоИдею в работу.
"""
revision_action_text = """
<b>Вы возвращаете на доработку НеоИдею:</b>
«{idea_name}».
<b>Автор:</b> {author_name}
Оставьте комментарий для того, чтобы отправить НеоИдею на доработку.
"""
send_revision_comment_text = """
Оставьте комментарий для того, чтобы отправить НеоИдею на доработку
"""
send_comment_text = """
Пожалуйста, оставьте свой комментарий:
"""
check_comment = """
Проверьте правильность введенных данных:
{comment}
"""
revision_action_send_text = """
Вы отправили НеоИдею на доработку.
"""
grade_action_text = """
<b>Вы направляете НеоИдею на оценку:</b>
«{idea_name}».
<b>Автор:</b> {author_name}
НеоИдея будет передана Куратору.
"""
grade_action_send_text = """
Спасибо! НеоИдея передана Куратору.
"""
reject_action_text = """
<b>Вы отклоняете НеоИдею:</b>
«{idea_name}».
<b>Автор:</b> {author_name}
Пожалуйста, оставьте свой комментарий.
"""
reject_action_send_text = """
Спасибо! НеоИдея отклонена.
"""
you_rejected = """
Здравствуйте, {name}.
Ваша НеоИдея "{idea_name}" была отклонена с комментарием:
"{comment}"
"""
you_revision = """
Ваша НеоИдея возвращена на доработку, пожалуйста проработайте комментарии от генерального директора и отправьте заявку заново.
НеоИдея: "{idea_name}"
Комментарий: "{comment}"
"""
accept_notify_author_text = """
Поздравляем! Ваша НеоИдея передана к внедрению
"""
accept_notify_curator_text = """
Быстрая НеоИдея автора {author_name} передана к внедрению {responsible_name}
"""
grade_curator_text = """
Вам на оценку передана НеоИдея "{idea_name}".
"""
confirm_notify_curator_text = """
Быстрая НеоИдея автора {author_name} передана к ответственному за категорию {responsible_name}
"""
confirm_notify_author_text = """
Ваша НеоИдея передана ответственному за категорию
"""
def confirm_action_ikb() -> InlineKeyboardMarkup:
"""
-Да, продолжить
-Нет, вернуться назад
:return: объект клавиатуры для параметра reply_markup
"""
builder = InlineKeyboardBuilder()
builder.add(
InlineKeyboardButton(
text='Да, продолжить ✅',
callback_data='confirm_action_confirm'
),
InlineKeyboardButton(
text='Нет, вернуться назад ❌',
callback_data='confirm_action_reject'
)
)
builder.adjust(1)
return builder.as_markup()
def choose_action_ikb(idea_id: str | int, msg_id: str | int) -> InlineKeyboardMarkup:
"""
-Согласовать и передать в работу
-Вернуть Автору на доработку
-Направить на оценку
-Отклонить
:param idea_id: ID идеи
:param msg_id: ID сообщения с клавиатурой
:return: объект клавиатуры для параметра reply_markup
"""
builder = InlineKeyboardBuilder()
builder.add(
InlineKeyboardButton(
text='Согласовать и передать в работу',
callback_data=f'moderation_confirm_{idea_id}_{msg_id}'
),
InlineKeyboardButton(
text='Вернуть Автору на доработку',
callback_data=f'moderation_revision_{idea_id}_{msg_id}'
),
InlineKeyboardButton(
text='Направить на оценку',
callback_data=f'moderation_grade_{idea_id}_{msg_id}'
),
InlineKeyboardButton(
text='Отклонить',
callback_data=f'moderation_reject_{idea_id}_{msg_id}'
)
)
builder.adjust(1)
return builder.as_markup()
async def choose_responsible_ikb(category_id: int) -> InlineKeyboardMarkup:
"""
-Ответственный 1
-Ответственный 2
:param category_id: ID категории идеи
:return: объект клавиатуры для параметра reply_markup
"""
builder = InlineKeyboardBuilder()
responsible_list = await get_data(
table_name='user_to_categories',
query_filter={'category_id': category_id}
)
for responsible in responsible_list:
responsible_data = await get_data(
table_name='users',
query_filter={'id': responsible['user_id']}
)
builder.add(
InlineKeyboardButton(
text=responsible_data[0]['full_name'],
callback_data=f'choose_responsible_{responsible_data[0]["id"]}',
)
)
builder.adjust(1)
return builder.as_markup()
async def send_new_idea_on_moderation(
idea_id: int, user_name: str, department: str, idea_title: str,
idea_content: str, relation_id: int, city_id: int, category_id: int, creation_date: datetime.datetime):
"""
Присылает новую идею модераторам из списка
:param idea_id: Данные идеи
:param user_name: Имя пользователя
:param department: Отдел пользователя
:param idea_title: Название идеи
:param idea_content: Описание идеи
:param relation_id: id отношения к идее
:param city_id: id города, где работает пользователь
:param category_id: id категории
:param creation_date: Дата создания идеи
:return:
"""
moderators = await get_data(
table_name='user_to_roles',
query_filter={'role_id': ROLE_MODERATOR_ID}
)
city = await get_data(
table_name='cities',
query_filter={'id': city_id}
)
files = await get_data(
table_name='idea_to_files',
query_filter={'idea_id': idea_id}
)
category = await get_data(
table_name='categories',
query_filter={'id': category_id}
)
notify_text = new_idea_data_text.format(
author_name=user_name,
creation_date=creation_date,
department=department,
idea_name=idea_title,
idea_content=idea_content,
city=city[0]['name'],
category=category[0]['name']
)
for moderator_id in moderators:
try:
moderator = await get_data(
table_name='users',
query_filter={'id': moderator_id['user_id']}
)
formatted_new_idea_text = new_idea_text.format(
name=moderator[0]['full_name'],
)
await bot.send_message(
chat_id=moderator[0]['telegram_id'],
text=formatted_new_idea_text
)
await bot.send_message(
chat_id=moderator[0]['telegram_id'],
text=notify_text
)
for file in files:
file_data = await get_data(
table_name='files',
query_filter={'id': file['file_id']}
)
await bot.send_document(
chat_id=moderator[0]['telegram_id'],
document=FSInputFile(FILES_PATH + file_data[0]['file'])
)
last_msg = await bot.send_message(
chat_id=moderator[0]['telegram_id'],
text=choose_action_text,
reply_markup=choose_action_ikb(
idea_id=idea_id,
msg_id=0
)
)
await bot.edit_message_reply_markup(
message_id=last_msg.message_id,
chat_id=moderator[0]['telegram_id'],
reply_markup=choose_action_ikb(
idea_id=idea_id,
msg_id=last_msg.message_id
)
)
moderator_user_data = await get_data(
table_name='users',
query_filter={'id': moderator_id['user_id']}
)
await notify(
user_id=moderator_user_data[0]['external_id'],
message='Новая быстрая НеоИдея!'
)
except Exception as e:
logging.error(e)
curators = await get_data(
table_name='user_to_roles',
query_filter={'role_id': ROLE_CURATOR_ID}
)
for curator in curators:
try:
user_data = await get_data(
table_name='users',
query_filter={'id': curator['user_id']}
)
await notify(
user_id=user_data[0]['external_id'],
message='Новая быстрая НеоИдея!'
)
except Exception as e:
logging.error(f'не могу отправить уведомление куратору о новой идее {e}')
async def accept_notify(author_name: str, responsible_id: str, author_id: int) -> None:
"""
Уведомляет куратора и автора о принятии идеи
:param responsible_id: ID кому передают идею
:param author_name: Имя автора идеи
:param author_id: ID автора
:return:
"""
responsible_data = await get_data(
table_name='users',
query_filter={'id': responsible_id}
)
notify_text = notify_curator_text.format(
author_name=author_name,
responsible_name=responsible_data[0]['full_name']
)
curators = await get_data(
table_name='user_to_roles',
query_filter={'role_id': ROLE_CURATOR_ID}
)
for curator in curators:
try:
user_data = await get_data(
table_name='users',
query_filter={'id': curator['user_id']}
)
await notify(
user_id=user_data[0]['external_id'],
message=notify_text
)
await bot.send_message(
chat_id=user_data[0]['telegram_id'],
text=notify_text
)
except Exception as e:
logging.error(f'не могу отправить уведомление куратору о принятии идеи {e}')
try:
author_data = await get_data(
table_name='users',
query_filter={'id': author_id}
)
await notify(
user_id=author_data[0]['external_id'],
message=notify_author_text
)
await bot.send_message(
chat_id=author_data[0]['telegram_id'],
text=notify_author_text
)
except Exception as e:
logging.error(f'Не смог отправить уведомление автору идеи {e}')
async def confirm_notify(author_name: str, responsible_id: str, author_id: int) -> None:
"""
Уведомляет куратора и автора о согласовании идеи
:param responsible_id: ID кому передают идею
:param author_name: Имя автора идеи
:param author_id: ID автора
:return:
"""
responsible_data = await get_data(
table_name='users',
query_filter={'id': responsible_id}
)
notify_text = confirm_notify_curator_text.format(
author_name=author_name,
responsible_name=responsible_data[0]['full_name']
)
curators = await get_data(
table_name='user_to_roles',
query_filter={'role_id': ROLE_CURATOR_ID}
)
for curator in curators:
try:
user_data = await get_data(
table_name='users',
query_filter={'id': curator['user_id']}
)
await notify(
user_id=user_data[0]['external_id'],
message=notify_text
)
await bot.send_message(
chat_id=user_data[0]['telegram_id'],
text=notify_text
)
except Exception as e:
logging.error(f'не могу отправить уведомление куратору о принятии идеи {e}')
try:
author_data = await get_data(
table_name='users',
query_filter={'id': author_id}
)
await notify(
user_id=author_data[0]['external_id'],
message=notify_author_text
)
await bot.send_message(
chat_id=author_data[0]['telegram_id'],
text=notify_author_text
)
except Exception as e:
logging.error(f'Не смог отправить уведомление автору идеи {e}')

0
utils/__init__.py Normal file
View File

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

250
utils/db.py Normal file
View File

@ -0,0 +1,250 @@
import logging
import asyncpg
import redis.asyncio as redis
from config import POSTGRES_NAME, POSTGRES_USER, POSTGRES_PASSWORD, POSTGRES_HOST, POSTGRES_PORT, REDIS_NAME, \
REDIS_HOST, REDIS_PORT, REDIS_PASSWORD
create_tables_query = """
CREATE TABLE IF NOT EXISTS ban_words
(id SERIAL PRIMARY KEY,
word TEXT NOT NULL);
CREATE TABLE IF NOT EXISTS message
(id SERIAL PRIMARY KEY,
text TEXT NOT NULL,
media TEXT,
buttons TEXT NOT NULL,
included BOOL NOT NULL);
CREATE TABLE IF NOT EXISTS ban_media
(id SERIAL PRIMARY KEY,
video BOOL NOT NULL,
photo BOOL NOT NULL);
"""
exist_query = """
SELECT EXISTS (SELECT FROM information_schema.tables WHERE table_name = $1)
"""
class Postgres:
def __init__(self):
self.conn = None
async def connect(self):
self.conn = await asyncpg.connect(
database=POSTGRES_NAME,
user=POSTGRES_USER,
password=POSTGRES_PASSWORD,
host=POSTGRES_HOST,
port=POSTGRES_PORT,
)
async def create_tables(self) -> None:
"""
Создаёт таблицы если их нет
:return:
"""
await self.connect()
try:
async with self.conn.transaction():
message_exist = await self.conn.fetchval(
exist_query, 'message'
)
ban_media_exist = await self.conn.fetchval(
exist_query, 'ban_media'
)
await self.conn.execute(
create_tables_query
)
if not message_exist:
await self.create_row(
table_name='message',
data_to_insert={
'text': 'Сообщение',
'media': '',
'buttons': '',
'included': False
}
)
if not ban_media_exist:
await self.create_row(
table_name='ban_media',
data_to_insert={
'photo': True,
'video': True,
}
)
except Exception as e:
logging.error(f'Ошибка в create_tables {e}')
finally:
await self.conn.close()
async def get_data(self, table_name: str, columns='*', query_filter=None) -> list:
"""
Получить данные нужной таблицы по указанным фильтрам
:param columns: Название колонн с нужными данными
:param query_filter: Фильтры запроса в формате {колонка: её значение}
:param table_name: Название таблицы для запроса
:return: False в случае ошибки, словарь с данными в случае успеха
"""
if query_filter is None:
query_filter = {}
await self.connect()
try:
if isinstance(columns, str):
columns = [columns]
full_query = f"SELECT {','.join(columns)} FROM {table_name}"
if query_filter:
query_filter = ' AND '.join(
[f"{key} = '{value}'" for key, value in query_filter.items()]
)
full_query += f' WHERE {query_filter}'
async with self.conn.transaction():
result = await self.conn.fetch(full_query)
return result
except Exception as e:
logging.error(f'Ошибка в get_data {e}')
finally:
await self.conn.close()
return []
async def update_data(self, new_data: dict, query_filter: dict, table_name: str) -> bool:
"""
Обновляет данные по заданным фильтрам
:param new_data: Новые данные в формате {Колонка: новое значение}
:param query_filter: Фильтры запроса в формате {колонка: её значение}
:param table_name: Название таблицы для запроса
:return: True в случае успеха, False в случае ошибки
"""
await self.connect()
try:
dollar_data = {key: f"${i + 1}" for i, key in enumerate(new_data)}
values = ', '.join(f'{key} = {value}' for key, value in dollar_data.items())
full_query = f"UPDATE {table_name} SET {values}"
if query_filter:
query_filter = ' AND '.join([f"{key} = '{value}'" for key, value in query_filter.items()])
full_query += f' WHERE {query_filter}'
async with self.conn.transaction():
await self.conn.execute(full_query, *new_data.values())
return True
except Exception as e:
logging.error(f'Ошибка в update_data {e}')
return False
finally:
await self.conn.close()
async def create_row(self, data_to_insert: dict, table_name: str) -> bool:
"""
Создаёт новую строку с данными
:param data_to_insert: Список, где ключ - название столбика, значение - значение столбика в новой строчке
:param table_name: Название таблицы, куда вставляем данные
:return: id последней вставленной строки
"""
await self.connect()
try:
dollars = [f"${i + 1}" for i in range(len(data_to_insert))]
full_query = f"INSERT INTO {table_name} ({', '.join(data_to_insert.keys())}) VALUES ({', '.join(dollars)})"
async with self.conn.transaction():
await self.conn.execute(full_query, *data_to_insert.values())
return True
except Exception as e:
logging.error(f'Ошибка в create_row {e}')
return False
finally:
await self.conn.close()
async def query(self, query_text: str):
"""
Прямой запрос к бд
:param query_text: sql запрос
:return: Результат sql запроса
"""
await self.connect()
try:
async with self.conn.transaction():
await self.conn.execute(query_text)
except Exception as e:
logging.error(f'Ошибка в query {e}')
finally:
await self.conn.close()
class Redis:
def __init__(self):
self.conn = None
async def connect(self):
try:
self.conn = await redis.Redis(
host=REDIS_HOST,
port=REDIS_PORT,
db=REDIS_NAME,
password=REDIS_PASSWORD,
decode_responses=True,
encoding='utf-8'
)
except Exception as e:
logging.error('redis connect', e)
async def delete_key(self, *keys: str | int) -> str | int:
await self.connect()
try:
return await self.conn.delete(*keys)
except Exception as e:
logging.error('redis delete_key', e)
finally:
await self.conn.close()
async def update_list(self, key: str | int, *values) -> str | int:
await self.connect()
try:
return await self.conn.rpush(key, *values)
except Exception as e:
logging.error('redis update_data', e)
finally:
await self.conn.close()
async def get_list(self, key: str | int) -> list:
await self.connect()
try:
data = await self.conn.lrange(name=str(key), start=0, end=-1)
return data
except Exception as e:
logging.error('redis get_data', e)
return []
finally:
await self.conn.close()
async def update_dict(self, key: str | int, value: dict) -> str | int:
await self.connect()
try:
return await self.conn.hset(name=str(key), mapping=value)
except Exception as e:
logging.error('redis update', e)
finally:
await self.conn.close()
async def get_dict(self, key: str | int) -> dict:
await self.connect()
try:
data = await self.conn.hgetall(name=str(key))
return data
except Exception as e:
logging.error('redis get', e)
return []
finally:
await self.conn.close()

44
utils/defs.py Normal file
View File

@ -0,0 +1,44 @@
import logging
from aiogram.types import Message
import pandas
from utils.db import Postgres
async def delete_msg(msg: Message) -> None:
"""
Безопасно удаляет сообщение
:param msg: Message
:return: True, если текст является ссылкой, иначе False.
"""
try:
await msg.delete()
except:
pass
async def create_xlsx() -> str:
"""
Составляет xlsx файл с данными бан слова
:return: Путь к готовому xlsx файлу
"""
try:
p = Postgres()
ban_words = await p.get_data(
table_name='ban_words'
)
table_dict = {'ID': [], 'Слово': []}
for ban_word in ban_words:
table_dict['ID'].append(ban_word['id'])
table_dict['Слово'].append(ban_word['word'])
df = pandas.DataFrame(table_dict)
file_path = 'data/ban_words.xlsx'
df.to_excel(file_path, index=False)
return file_path
except Exception as e:
logging.error('Ошибка в create_xlsx', e)

73
utils/middleware.py Normal file
View File

@ -0,0 +1,73 @@
# from aiogram import BaseMiddleware
# from aiogram.types import TelegramObject, Update
#
# from typing import Callable, Dict, Any, Awaitable
# from utils.defs import delete_msg
# from utils.db import Postgres, Redis
# from templates.message import send_preview
#
#
# class DeleteMessage(BaseMiddleware):
# """
# Мидлвари удаляющая сообщения с бан вордами
# """
#
# async def __call__(
# self,
# handler: Callable[[TelegramObject, Dict[str, Any]], Awaitable[Any]],
# event: Update,
# data: Dict[str, Any]
# ) -> Any:
# if not event.message:
# return await handler(event, data)
# if not (event.message.chat.type == 'group' or
# event.message.chat.type == 'supergroup'):
# return await handler(event, data)
#
# ban = False
# r = Redis()
# p = Postgres()
#
# ban_words = await r.get_list(
# key='ban_words'
# )
# if not ban_words:
# ban_words = await p.get_data(
# table_name='ban_words'
# )
# ban_words = [word['word'] for word in ban_words]
# await r.delete_key(
# 'ban_words'
# )
# await r.update_list(
# 'ban_words',
# *ban_words
# )
#
# for ban_word in ban_words:
# print(ban_word)
# if ban_word in event.message.text.lower():
# print(event.message.text.lower(), 'нашёл')
# ban = True
#
# if ban:
# await delete_msg(
# msg=event.message
# )
# message_data = await p.get_data(
# table_name='message'
# )
#
# if event.message.from_user.username:
# username = f'@{event.message.from_user.username}'
# else:
# username = event.message.from_user.full_name
#
# if message_data[0]['included']:
# await send_preview(
# chat_id=event.message.chat.id,
# message_data=message_data[0],
# username=username
# )
#
# return await handler(event, data)