Initial commit
This commit is contained in:
parent
7a1a025471
commit
a7bfd5ed4a
13
Dockerfile
Normal file
13
Dockerfile
Normal 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"]
|
36
README.md
36
README.md
@ -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
27
config.py
Normal 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
8
core.py
Normal 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
BIN
data/ban_words.xlsx
Normal file
Binary file not shown.
33
docker-compose.yml
Normal file
33
docker-compose.yml
Normal 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
8
handlers/__init__.py
Normal 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]
|
BIN
handlers/__pycache__/__init__.cpython-310.pyc
Normal file
BIN
handlers/__pycache__/__init__.cpython-310.pyc
Normal file
Binary file not shown.
BIN
handlers/__pycache__/__init__.cpython-311.pyc
Normal file
BIN
handlers/__pycache__/__init__.cpython-311.pyc
Normal file
Binary file not shown.
BIN
handlers/__pycache__/ban_media.cpython-311.pyc
Normal file
BIN
handlers/__pycache__/ban_media.cpython-311.pyc
Normal file
Binary file not shown.
BIN
handlers/__pycache__/ban_words.cpython-311.pyc
Normal file
BIN
handlers/__pycache__/ban_words.cpython-311.pyc
Normal file
Binary file not shown.
BIN
handlers/__pycache__/come_back.cpython-311.pyc
Normal file
BIN
handlers/__pycache__/come_back.cpython-311.pyc
Normal file
Binary file not shown.
BIN
handlers/__pycache__/commands.cpython-310.pyc
Normal file
BIN
handlers/__pycache__/commands.cpython-310.pyc
Normal file
Binary file not shown.
BIN
handlers/__pycache__/commands.cpython-311.pyc
Normal file
BIN
handlers/__pycache__/commands.cpython-311.pyc
Normal file
Binary file not shown.
BIN
handlers/__pycache__/group.cpython-311.pyc
Normal file
BIN
handlers/__pycache__/group.cpython-311.pyc
Normal file
Binary file not shown.
BIN
handlers/__pycache__/message.cpython-311.pyc
Normal file
BIN
handlers/__pycache__/message.cpython-311.pyc
Normal file
Binary file not shown.
66
handlers/ban_media.py
Normal file
66
handlers/ban_media.py
Normal 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
134
handlers/ban_words.py
Normal 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
115
handlers/come_back.py
Normal 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
39
handlers/commands.py
Normal 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
92
handlers/group.py
Normal 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
252
handlers/message.py
Normal 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
36
main.py
Normal 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
7
requirements.txt
Normal 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
0
templates/__init__.py
Normal file
BIN
templates/__pycache__/__init__.cpython-311.pyc
Normal file
BIN
templates/__pycache__/__init__.cpython-311.pyc
Normal file
Binary file not shown.
BIN
templates/__pycache__/ban_words.cpython-311.pyc
Normal file
BIN
templates/__pycache__/ban_words.cpython-311.pyc
Normal file
Binary file not shown.
BIN
templates/__pycache__/commands.cpython-311.pyc
Normal file
BIN
templates/__pycache__/commands.cpython-311.pyc
Normal file
Binary file not shown.
BIN
templates/__pycache__/message.cpython-311.pyc
Normal file
BIN
templates/__pycache__/message.cpython-311.pyc
Normal file
Binary file not shown.
64
templates/ban_words.py
Normal file
64
templates/ban_words.py
Normal 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
62
templates/commands.py
Normal 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
226
templates/message.py
Normal 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
458
templates/moderation.py
Normal 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
0
utils/__init__.py
Normal file
BIN
utils/__pycache__/__init__.cpython-310.pyc
Normal file
BIN
utils/__pycache__/__init__.cpython-310.pyc
Normal file
Binary file not shown.
BIN
utils/__pycache__/__init__.cpython-311.pyc
Normal file
BIN
utils/__pycache__/__init__.cpython-311.pyc
Normal file
Binary file not shown.
BIN
utils/__pycache__/db.cpython-311.pyc
Normal file
BIN
utils/__pycache__/db.cpython-311.pyc
Normal file
Binary file not shown.
BIN
utils/__pycache__/defs.cpython-311.pyc
Normal file
BIN
utils/__pycache__/defs.cpython-311.pyc
Normal file
Binary file not shown.
BIN
utils/__pycache__/middleware.cpython-311.pyc
Normal file
BIN
utils/__pycache__/middleware.cpython-311.pyc
Normal file
Binary file not shown.
250
utils/db.py
Normal file
250
utils/db.py
Normal 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
44
utils/defs.py
Normal 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
73
utils/middleware.py
Normal 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)
|
Loading…
Reference in New Issue
Block a user