add help, start command.
add auto-detect message and immage. add logger. add db. add use .env file.
This commit is contained in:
+11
@@ -0,0 +1,11 @@
|
||||
from aiogram import Bot
|
||||
from aiogram.client.default import DefaultBotProperties
|
||||
from aiogram.fsm.storage.memory import MemoryStorage
|
||||
from aiogram.enums import ParseMode
|
||||
from config.settings import BOT_TOKEN
|
||||
|
||||
bot = Bot(
|
||||
token=BOT_TOKEN,
|
||||
default=DefaultBotProperties(parse_mode=ParseMode.HTML)
|
||||
)
|
||||
storage = MemoryStorage()
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
from aiogram import Router, types
|
||||
|
||||
router = Router()
|
||||
|
||||
|
||||
@router.chat_join_request()
|
||||
async def new_chat_handler(event: types.ChatJoinRequest):
|
||||
"""
|
||||
Обработчик события, когда бот добавляется в новый чат.
|
||||
|
||||
:param event: Объект события добавления в чат
|
||||
:return: None
|
||||
"""
|
||||
chat_name = event.chat.title or "группу"
|
||||
bot_member = await event.bot.get_chat_member(event.chat.id, event.bot.id)
|
||||
|
||||
if not bot_member.is_chat_admin():
|
||||
await event.bot.send_message(
|
||||
event.chat.id,
|
||||
f"<b>Спасибо за добавление меня в {chat_name}!</b>\n"
|
||||
"Однако, чтобы я мог выполнять свои функции, "
|
||||
"мне необходимы права администратора.\n"
|
||||
"Пожалуйста, предоставьте мне соответствующие права.",
|
||||
parse_mode="HTML"
|
||||
)
|
||||
else:
|
||||
await event.bot.send_message(
|
||||
event.chat.id,
|
||||
f"<b>Спасибо за добавление меня в {chat_name}!</b>\n"
|
||||
"Я готов помогать модерировать и защищать ваш чат.",
|
||||
parse_mode="HTML"
|
||||
)
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
from aiogram import Router, types
|
||||
from aiogram.filters import Command
|
||||
|
||||
router = Router()
|
||||
|
||||
|
||||
@router.message(Command("help"))
|
||||
async def help_command(message: types.Message):
|
||||
"""
|
||||
Обработчик команды /help.
|
||||
|
||||
:param message: Объект сообщения от пользователя
|
||||
:return: None
|
||||
"""
|
||||
help_message = (
|
||||
"<b>Команды и возможности бота:</b>\n"
|
||||
"\n"
|
||||
"<i>Основные команды:</i>\n"
|
||||
"- /start — Начать работу с ботом\n"
|
||||
"- /help — Показать это сообщение\n"
|
||||
"\n"
|
||||
"<i>Модерация:</i>\n"
|
||||
"- Автоматическая фильтрация спама и неуместного контента\n"
|
||||
"- Уведомления для администраторов о подозрительной активности\n"
|
||||
"\n"
|
||||
"Добавьте бота в чат, назначьте администратором и настройте его права.\n"
|
||||
"Если у вас возникнут вопросы, свяжитесь с разработчиком."
|
||||
)
|
||||
await message.answer(help_message, parse_mode="HTML")
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
from aiogram import Router, types
|
||||
from aiogram.filters import Command
|
||||
|
||||
router = Router()
|
||||
|
||||
|
||||
@router.message(Command("start"))
|
||||
async def start_command(message: types.Message):
|
||||
"""
|
||||
Обработчик команды /start.
|
||||
|
||||
:param message: Объект сообщения от пользователя
|
||||
:return: None
|
||||
"""
|
||||
welcome_message = (
|
||||
"<b>Привет, {name}!</b>\n"
|
||||
"Я бот-модератор, созданный для помощи в управлении чатами.\n"
|
||||
"\n"
|
||||
"<i>Вот что я умею:</i>\n"
|
||||
"- Модерировать сообщения на основе встроенного ИИ\n"
|
||||
"- Удалять нежелательный контент\n"
|
||||
"- Уведомлять администраторов о подозрительных действиях\n"
|
||||
"\n"
|
||||
"Добавьте меня в чат и настройте права администратора, "
|
||||
"чтобы я мог начать работу.\n"
|
||||
"Введите /help, чтобы узнать больше о моих возможностях."
|
||||
)
|
||||
await message.answer(
|
||||
welcome_message.format(name=message.from_user.first_name),
|
||||
parse_mode="HTML"
|
||||
)
|
||||
|
||||
@@ -0,0 +1,121 @@
|
||||
import aiohttp
|
||||
from aiogram import Router, types, F
|
||||
from aiogram.enums import ContentType
|
||||
|
||||
from bot.utils.actions import BotActions
|
||||
from bot.utils.api_client import ApiClient
|
||||
from bot.utils.logger import get_logger
|
||||
from config.settings import (
|
||||
BOT_PROXY_IP,
|
||||
BOT_PROXY_PASSWORD,
|
||||
BOT_PROXY_SOCKS5_PORT,
|
||||
BOT_PROXY_USER
|
||||
)
|
||||
|
||||
router = Router()
|
||||
|
||||
|
||||
class MessageHandler:
|
||||
def __init__(self):
|
||||
self.api_client = ApiClient()
|
||||
self.bot_actions = BotActions()
|
||||
self.logger = get_logger(__name__)
|
||||
|
||||
async def handle_text_message(self, message: types.Message) -> bool:
|
||||
"""
|
||||
Обработка текстового сообщения.
|
||||
|
||||
:param message: Объект сообщения Telegram
|
||||
:return: True, если обработка успешна, иначе False
|
||||
"""
|
||||
try:
|
||||
print(message.text)
|
||||
response = await self.api_client.check_message(message.text)
|
||||
print(response)
|
||||
if response:
|
||||
category = response.get('category')
|
||||
likelihood = response.get('likelihood')
|
||||
|
||||
if likelihood > 0.7:
|
||||
chat_id = str(message.chat.id)
|
||||
await self.bot_actions.handle_actions(chat_id, category, message)
|
||||
return True
|
||||
except Exception as e:
|
||||
self.logger.error(f"Ошибка при обработке текста: {e}")
|
||||
return False
|
||||
|
||||
async def handle_image_message(self, message: types.Message) -> bool:
|
||||
"""
|
||||
Обработка сообщения с изображениями.
|
||||
|
||||
:param message: Объект сообщения Telegram
|
||||
:return: True, если обработка успешна, иначе False
|
||||
"""
|
||||
try:
|
||||
if message.photo:
|
||||
photo = message.photo[-1]
|
||||
file_info = await message.bot.get_file(photo.file_id)
|
||||
file_path = file_info.file_path
|
||||
image_url = (
|
||||
f"https://api.telegram.org/file/"
|
||||
f"bot{message.bot.token}/{file_path}"
|
||||
)
|
||||
image_data = await self.download_image(image_url)
|
||||
|
||||
response = await self.api_client.check_image(image_data)
|
||||
if response:
|
||||
category = response.get('category')
|
||||
likelihood = response.get('likelihood')
|
||||
|
||||
if likelihood > 0.7:
|
||||
chat_id = str(message.chat.id)
|
||||
await self.bot_actions.handle_actions(
|
||||
chat_id,
|
||||
category,
|
||||
message
|
||||
)
|
||||
return True
|
||||
except Exception as e:
|
||||
self.logger.error(f"Ошибка при обработке изображения: {e}")
|
||||
return False
|
||||
|
||||
async def download_image(self, url: str) -> bytes | None:
|
||||
"""
|
||||
Асинхронная загрузка изображения по URL через SOCKS5 прокси.
|
||||
|
||||
:param url: Ссылка на изображение
|
||||
:return: Байтовые данные изображения
|
||||
"""
|
||||
try:
|
||||
proxy_url = (
|
||||
f"socks5://{BOT_PROXY_USER}:{BOT_PROXY_PASSWORD}"
|
||||
f"@{BOT_PROXY_IP}:{BOT_PROXY_SOCKS5_PORT}"
|
||||
)
|
||||
connector = aiohttp.ProxyConnector.from_url(proxy_url)
|
||||
|
||||
async with aiohttp.ClientSession(connector=connector) as session:
|
||||
async with session.get(url) as response:
|
||||
if response.status == 200:
|
||||
return await response.read()
|
||||
except Exception as e:
|
||||
self.logger.error(f"Ошибка при загрузке изображения: {e}")
|
||||
return None
|
||||
|
||||
|
||||
@router.message(
|
||||
F.content_type == ContentType.TEXT or
|
||||
F.content_type == ContentType.PHOTO
|
||||
)
|
||||
async def on_message(message: types.Message):
|
||||
"""
|
||||
Обработка всех сообщений.
|
||||
|
||||
:param message: Объект сообщения Telegram
|
||||
:return: None
|
||||
"""
|
||||
handler = MessageHandler()
|
||||
|
||||
if message.text:
|
||||
await handler.handle_text_message(message)
|
||||
if message.photo:
|
||||
await handler.handle_image_message(message)
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
class BotActions:
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
from typing import Any
|
||||
|
||||
import aiohttp
|
||||
|
||||
from bot.utils.logger import get_logger
|
||||
from config.settings import API_TOKEN, API_URL
|
||||
|
||||
|
||||
class ApiClient:
|
||||
"""
|
||||
Класс для работы с api модуля core.
|
||||
"""
|
||||
def __init__(self) -> None:
|
||||
self.api_url = API_URL
|
||||
self.api_key = API_TOKEN
|
||||
self.logger = get_logger(__name__)
|
||||
|
||||
@staticmethod
|
||||
def get_max_likelihood(data: dict[str, dict[str, str]]) -> dict[str, str]:
|
||||
"""
|
||||
Возвращает категорию и наибольшее значение вероятности из переданных данных.
|
||||
|
||||
:param data: Словарь, где ключи — идентификаторы,
|
||||
а значения — вложенные словари с категориями и вероятностями
|
||||
:return: Словарь с максимальной категорией и её вероятностью
|
||||
"""
|
||||
max_item = max(data.values(), key=lambda x: x['likelihood'])
|
||||
return {'category': max_item['category'], 'likelihood': max_item['likelihood']}
|
||||
|
||||
async def check_message(self, message_content: str) -> dict[str, Any]:
|
||||
"""
|
||||
Проверяет сообщение, отправляя его на API для анализа,
|
||||
и возвращает категорию с максимальной вероятностью.
|
||||
|
||||
:param message_content: Текст сообщения для проверки
|
||||
:return: Словарь с категорией и наибольшей вероятностью
|
||||
:raises Exception: Если API возвращает статус ошибки
|
||||
"""
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with session.post(
|
||||
url=self.api_url,
|
||||
headers={
|
||||
"Authorization": f"Api-Key {self.api_key}"
|
||||
},
|
||||
data={
|
||||
"text": message_content
|
||||
},
|
||||
ssl=False
|
||||
) as response:
|
||||
if response.status == 200:
|
||||
response_text = await response.json()
|
||||
max_likelihood = self.get_max_likelihood(response_text)
|
||||
return max_likelihood
|
||||
else:
|
||||
self.logger.error(f"❌ Произошла ошибка API: {response.status}")
|
||||
raise Exception(f"API Error: {response.status}")
|
||||
|
||||
async def check_image(self, image_content: bytes) -> dict[str, Any]:
|
||||
"""
|
||||
Проверяет изображение, отправляя его на API для анализа,
|
||||
и возвращает категорию с максимальной вероятностью.
|
||||
|
||||
:param image_content: Изображение для проверки
|
||||
:return: Словарь с категорией и наибольшей вероятностью
|
||||
:raises Exception: Если API возвращает статус ошибки
|
||||
"""
|
||||
async with aiohttp.ClientSession() as session:
|
||||
form_data = aiohttp.FormData()
|
||||
form_data.add_field(
|
||||
name="file",
|
||||
value=image_content,
|
||||
filename="image.jpg",
|
||||
content_type="image/jpeg"
|
||||
)
|
||||
async with session.post(
|
||||
url=self.api_url,
|
||||
headers={
|
||||
"Authorization": f"Api-Key {self.api_key}"
|
||||
},
|
||||
data=form_data,
|
||||
ssl=False
|
||||
) as response:
|
||||
if response.status == 200:
|
||||
response_text = await response.json()
|
||||
max_likelihood = self.get_max_likelihood(response_text)
|
||||
return max_likelihood
|
||||
else:
|
||||
self.logger.error(f"❌ Произошла ошибка API: {response.status}")
|
||||
raise Exception(f"API Error: {response.status}")
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
import os
|
||||
|
||||
from pymongo import MongoClient
|
||||
|
||||
|
||||
class UseDB:
|
||||
"""
|
||||
Класс для работы с базой данных
|
||||
"""
|
||||
|
||||
def __init__(self, collection_name):
|
||||
self.series_collection = None
|
||||
if os.getenv('ENV') == 'production':
|
||||
self.client = MongoClient('mongodb', 27017) # pragma: nocover
|
||||
else:
|
||||
self.client = MongoClient('localhost', 27017)
|
||||
self.db = self.client['moderator']
|
||||
self.series_collection = self.db[collection_name]
|
||||
|
||||
def find_document(self, elements: dict) -> list:
|
||||
"""
|
||||
Функция поиска элемента в базе данных
|
||||
|
||||
:param elements: dict - элемент, который надо искать
|
||||
|
||||
:return: list - сам элемент
|
||||
"""
|
||||
return [x for x in self.series_collection.find(elements)]
|
||||
|
||||
def insert_document(self, data: dict) -> list:
|
||||
"""
|
||||
Функция добавления элемента в базу данных
|
||||
|
||||
:param data: dict - элемент, который надо добавить
|
||||
|
||||
:return: list - созданный элемент
|
||||
"""
|
||||
return self.series_collection.insert_one(data).inserted_id
|
||||
|
||||
def update_document(self, query_elements: dict, new_values: dict) -> None:
|
||||
"""
|
||||
Функция обновления элемента в базе данных
|
||||
|
||||
:param query_elements: dict - элемент, который надо обновить
|
||||
|
||||
:param new_values: dict - элемент, этот самый элемент
|
||||
|
||||
:return: None
|
||||
"""
|
||||
self.series_collection.update_one(query_elements, {'$set': new_values})
|
||||
|
||||
def update_all_document(self, query_elements: dict, new_values: dict) -> None:
|
||||
"""
|
||||
Функция обновления элемента в базе данных
|
||||
|
||||
:param query_elements: dict - элемент, который надо обновить
|
||||
|
||||
:param new_values: dict - элемент, этот самый элемент
|
||||
|
||||
:return: None
|
||||
"""
|
||||
self.series_collection.update_one(query_elements, new_values)
|
||||
|
||||
def del_document(self, query_elements: dict) -> None:
|
||||
"""
|
||||
Функция удаления элемента в базу данных
|
||||
|
||||
:param query_elements: dict - элемент, который надо удалить
|
||||
|
||||
:return: None
|
||||
"""
|
||||
self.series_collection.delete_one(query_elements)
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
from loguru import logger
|
||||
|
||||
LOG_FILE = Path("bot.log")
|
||||
|
||||
|
||||
def configure_logger():
|
||||
logger.remove()
|
||||
|
||||
logger.add(
|
||||
sys.stdout,
|
||||
format="<level>[{level}]</level> <green>{time:YYYY-MM-DD HH:mm:ss}</green> | "
|
||||
"<cyan>{name}</cyan>: <level>{message}</level>",
|
||||
level="DEBUG",
|
||||
colorize=True,
|
||||
)
|
||||
|
||||
logger.add(
|
||||
LOG_FILE,
|
||||
format="[ {level} ] {time:YYYY-MM-DD HH:mm:ss} | {name}: {message}",
|
||||
level="INFO",
|
||||
rotation="5 MB",
|
||||
retention="5 days",
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
logger.add(
|
||||
sys.stderr,
|
||||
format="{time:YYYY-MM-DDTHH:mm:ssZ} [{level}] {name}: {message}",
|
||||
level="DEBUG",
|
||||
colorize=False,
|
||||
)
|
||||
|
||||
|
||||
configure_logger()
|
||||
|
||||
|
||||
def get_logger(name: str) -> logger:
|
||||
return logger.bind(name=name)
|
||||
|
||||
Reference in New Issue
Block a user