типо да

This commit is contained in:
admin
2025-09-08 00:40:18 +07:00
commit 0f05fc8455
83 changed files with 5775 additions and 0 deletions

5
bot/utils/__init__.py Normal file
View File

@@ -0,0 +1,5 @@
from .interesting_facts import *
from .usernames import *
from .pagination import *
from .type_message import *
from .argument import *

59
bot/utils/argument.py Normal file
View File

@@ -0,0 +1,59 @@
from __future__ import annotations
from typing import Optional
from configs import BotSettings
__all__ = ("is_command", "find_argument")
def is_command(message: Optional[str]) -> bool:
"""
Проверяет, является ли сообщение командой.
Сообщение считается командой, если:
1. Оно не пустое;
2. Начинается с префикса команды, указанного в настройках.
Args:
message (Optional[str]): Входное сообщение.
Returns:
bool: True, если сообщение является командой, иначе False.
Пример:
>>> is_command("/start")
True
>>> is_command("hello")
False
"""
if not message:
return False
return message.strip().startswith(BotSettings.PREFIX)
def find_argument(message: Optional[str]) -> Optional[str]:
"""
Извлекает аргумент команды из сообщения.
Аргументом считается текст после первой команды и пробела.
Если аргумента нет — возвращает None.
Args:
message (Optional[str]): Входное сообщение.
Returns:
Optional[str]: Аргумент команды или None, если его нет.
Пример:
>>> find_argument("/start referrer")
'referrer'
>>> find_argument("/start")
None
>>> find_argument("hello")
None
"""
if not is_command(message):
return None
parts = message.strip().split(maxsplit=1)
return parts[1] if len(parts) > 1 else None

View File

@@ -0,0 +1,54 @@
from random import choice
from typing import Dict, List, Optional
from configs.config import Lists
__all__ = ("interesting_fact", "get_best_response",)
def interesting_fact(mode: str = "факт", lists: Optional[list[str]] = None) -> str:
"""
Возвращает случайный факт, анекдот или цитату, в зависимости от режима.
:param mode: строка, определяющая тип контента ("факт", "анекдот", "цитата").
:param lists: необязательный список строк, из которого можно выбирать вручную.
:return: случайный элемент из соответствующего списка.
"""
if lists is not None:
return choice(lists)
mode = mode.lower()
if mode == "анекдот":
source: list[str] = Lists.jokes
elif mode == "цитата":
source: list[str] = Lists.quotes
else:
source: list[str] = Lists.facts
return choice(source)
def get_best_response(
user_text: str,
responses: Dict[str, Dict[str, List[str]]],
random_phrases: List[str],
) -> str:
"""
Подбирает наиболее подходящий ответ на сообщение пользователя.
Сначала ищет ключевые слова и их синонимы, если совпадений нет — выдаёт случайную фразу.
:param user_text: текст сообщения пользователя
:param responses: словарь с ключевыми словами и ответами
:param random_phrases: список случайных фраз, если совпадений нет
:return: строка с ответом
"""
normalized_text: str = user_text.lower()
# Перебор ключевых слов в словаре
for _, data in responses.items():
for keyword in data["keywords"]:
if keyword in normalized_text:
return choice(data["answers"])
# Если совпадений нет — выдаём случайную фразу
return choice(random_phrases)

28
bot/utils/pagination.py Normal file
View File

@@ -0,0 +1,28 @@
from aiogram.types import InlineKeyboardButton
# Настройка экспорта в модули
__all__ = ('pagination_btn',)
def pagination_btn(action: str,
page: int = 0,
total_posts: int = 0,
bt_page: int = 5) -> list[InlineKeyboardButton]:
"""
Создает кнопки для пагинации.
:param action: Действие в котором нужна пангинация.
:param page: Номер начальной страницы, по умолчанию 0.
:param total_posts: Количество постов.
:param bt_page: Количество кнопок на одной странице.
:return: Готовый лист списка инлайн-кнопок.
"""
navigation_buttons: list[InlineKeyboardButton] = []
if page > 0:
navigation_buttons.append(InlineKeyboardButton(
text="", callback_data=f"{action}_page_{page - 1}"
))
if (page + 1) * bt_page < total_posts:
navigation_buttons.append(InlineKeyboardButton(
text="", callback_data=f"{action}_page_{page + 1}"
))
return navigation_buttons

18
bot/utils/random_lists.py Normal file
View File

@@ -0,0 +1,18 @@
from random import choice
def get_best_response(user_text: str) -> str:
"""
Подбирает наиболее подходящий ответ на сообщение пользователя.
Сначала ищет ключевые слова и их синонимы, если совпадений нет — выдаёт случайную фразу.
:param user_text: текст сообщения пользователя
:return: строка с ответом
"""
normalized_text: str = user_text.lower()
for _, data in RESPONSES.items():
for keyword in data["keywords"]:
if keyword in normalized_text:
return choice(data["answers"])
return choice(RANDOM_PHRASES)

85
bot/utils/type_message.py Normal file
View File

@@ -0,0 +1,85 @@
from typing import Final
from aiogram.types import Message
# Настройка экспорта
__all__ = ("CHAT_TYPES", "CONTENT_TYPE_RU", "type_chat", "type_msg")
# Словарь сопоставлений "chat_type -> русское название"
CHAT_TYPES: Final[dict[str, str]] = {
"private": "Личный",
"group": "Группа",
"supergroup": "Группа",
"channel": "Канал",
}
# Словарь сопоставлений "content_type -> русское название"
CONTENT_TYPE_RU: Final[dict[str, str]] = {
"text": "Текст",
"animation": "Гиф",
"audio": "Аудио",
"document": "Файл",
"photo": "Фото",
"sticker": "Стикер",
"video": "Видео",
"video_note": "Видеосообщение",
"voice": "Голосовое сообщение",
"contact": "Контакт",
"dice": "Кубик",
"game": "Игра",
"poll": "Опрос",
"venue": "Место",
"location": "Локация",
"new_chat_members": "Новые участники чата",
"left_chat_member": "Участник вышел",
"new_chat_title": "Новое название чата",
"new_chat_photo": "Новая картинка чата",
"delete_chat_photo": "Удалена картинка чата",
"group_chat_created": "Создана группа",
"supergroup_chat_created": "Создана супергруппа",
"channel_chat_created": "Создан канал",
"message_auto_delete_timer_changed": "Изменён автоудалитель",
"migrate_to_chat_id": "Группа → супергруппа",
"migrate_from_chat_id": "Супергруппа → группа",
"pinned_message": "Закреплённое сообщение",
"invoice": "Счёт",
"successful_payment": "Успешный платёж",
"connected_website": "Подключённый сайт",
"passport_data": "Данные Telegram Passport",
"proximity_alert_triggered": "Алерт о приближении",
"video_chat_scheduled": "Запланированный видеочат",
"video_chat_started": "Видеочат начался",
"video_chat_ended": "Видеочат завершён",
"video_chat_participants_invited": "Приглашены участники видеочата",
"web_app_data": "Данные из веб-приложения",
"forum_topic_created": "Создана тема форума",
"forum_topic_edited": "Изменена тема форума",
"forum_topic_closed": "Тема форума закрыта",
"forum_topic_reopened": "Тема форума открыта",
"general_forum_topic_hidden": "Общая тема скрыта",
"general_forum_topic_unhidden": "Общая тема снова отображается",
"giveaway_created": "Создан розыгрыш",
"giveaway": "Розыгрыш",
"giveaway_completed": "Розыгрыш завершён",
"message_reaction": "Реакция на сообщение",
}
def type_msg(message: Message) -> str:
"""
Определяет и возвращает тип сообщения на русском языке.
:param message: объект Message от aiogram
:return: строка с типом сообщения
"""
return CONTENT_TYPE_RU.get(message.content_type, f"Неизвестный тип ({message.content_type})")
def type_chat(message: Message) -> str:
"""
Преобразует информацию о чате в его тип на русском языке.
:param message: Объект сообщения из aiogram, содержащий информацию о чате.
:return: Тип чата строкой.
"""
return CHAT_TYPES.get(message.chat.type, f"Неизвестный тип чата {message.chat.type}")

21
bot/utils/usernames.py Normal file
View File

@@ -0,0 +1,21 @@
from aiogram.types import Message
# Настройка экспорта в модули
__all__ = ('username', )
# Функция получения юзера или ID пользователя
def username(message: Message) -> str:
"""
Возвращает юзернейм пользователя из сообщения, или ID, если юзернейм не указан.
:param message: Объект сообщения из aiogram.
:return: Строка с юзернеймом пользователя или его ID.
:raises ValueError: Если в сообщении отсутствует информация о пользователе.
"""
try:
if message.from_user:
return f"@{message.from_user.username}" if message.from_user.username else f"@{message.from_user.id}"
raise ValueError("Информация о пользователе отсутствует в сообщении.")
except ValueError as e:
raise e # Перебрасываем ошибку выше для дальнейшей обработки