add help, start command.

add auto-detect message and immage.
add logger.
add db.
add use .env file.
This commit is contained in:
2024-12-21 22:20:05 +07:00
parent 9154c25a40
commit cf7ba9e059
13 changed files with 514 additions and 31 deletions
+11
View File
@@ -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()
+32
View File
@@ -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"
)
+29
View File
@@ -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")
+31
View File
@@ -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"
)
+121
View File
@@ -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)
View File
+3
View File
@@ -0,0 +1,3 @@
class BotActions:
def __init__(self):
pass
+89
View File
@@ -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}")
+72
View File
@@ -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)
+41
View File
@@ -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)