Добавлены новые методы для получения статистики постов пользователей, информации о последних постах и количестве банов. Обновлены запросы в репозиториях для сортировки пользователей по дате бана. Исправлены вызовы функций форматирования сообщений для администраторов. Обновлены тесты для проверки новых функциональностей.
This commit is contained in:
@@ -156,6 +156,92 @@ class UserService:
|
||||
username = message.from_user.username or "Без никнейма"
|
||||
return html.escape(full_name), html.escape(username)
|
||||
|
||||
async def format_user_message_for_admins(
|
||||
self, user_id: int, full_name: str, username: str, message_text: str
|
||||
) -> str:
|
||||
"""
|
||||
Форматирует сообщение пользователя для отправки админам с обогащёнными данными.
|
||||
|
||||
Args:
|
||||
user_id: ID пользователя
|
||||
full_name: Полное имя пользователя
|
||||
username: Username пользователя (может быть None)
|
||||
message_text: Текст сообщения пользователя
|
||||
|
||||
Returns:
|
||||
Отформатированное сообщение для админов
|
||||
"""
|
||||
safe_full_name = html.escape(full_name) if full_name else "Неизвестный пользователь"
|
||||
safe_username = html.escape(username) if username else None
|
||||
safe_message_text = html.escape(message_text) if message_text else ""
|
||||
|
||||
# Формируем строку с информацией об авторе
|
||||
if safe_username:
|
||||
author_info = f"{safe_full_name} (@{safe_username})"
|
||||
else:
|
||||
author_info = f"{safe_full_name} (Ник не указан)"
|
||||
|
||||
# Получаем статистику постов
|
||||
approved, declined, suggest = await self.db.get_user_posts_stats(user_id)
|
||||
total_posts = approved + declined + suggest
|
||||
|
||||
# Получаем последний пост
|
||||
last_post = await self.db.get_last_post_by_author(user_id)
|
||||
if last_post:
|
||||
if len(last_post) > 80:
|
||||
last_post_display = f'"{html.escape(last_post[:80])}..."'
|
||||
else:
|
||||
last_post_display = f'"{html.escape(last_post)}"'
|
||||
else:
|
||||
last_post_display = "Нет постов"
|
||||
|
||||
# Получаем дату регистрации
|
||||
user_info = await self.db.get_user_by_id(user_id)
|
||||
if user_info and user_info.date_added:
|
||||
date_added = datetime.fromtimestamp(user_info.date_added).strftime("%d.%m.%Y")
|
||||
else:
|
||||
date_added = "Неизвестно"
|
||||
|
||||
# Получаем информацию о банах
|
||||
ban_count = await self.db.get_user_ban_count(user_id)
|
||||
ban_section = ""
|
||||
if ban_count > 0:
|
||||
last_ban = await self.db.get_last_ban_info(user_id)
|
||||
if last_ban:
|
||||
date_ban, reason, date_unban = last_ban
|
||||
ban_date_str = datetime.fromtimestamp(date_ban).strftime("%d.%m.%Y")
|
||||
reason_display = html.escape(reason) if reason else "Не указана"
|
||||
|
||||
if date_unban:
|
||||
unban_date_str = datetime.fromtimestamp(date_unban).strftime(
|
||||
"%d.%m.%Y %H:%M"
|
||||
)
|
||||
last_ban_info = (
|
||||
f" Последний: {ban_date_str}, причина «{reason_display}», "
|
||||
f"истёк {unban_date_str}"
|
||||
)
|
||||
else:
|
||||
last_ban_info = (
|
||||
f" Последний: {ban_date_str}, причина «{reason_display}», "
|
||||
f"активен"
|
||||
)
|
||||
|
||||
ban_section = f"\n\n🚫 Банов: {ban_count}\n{last_ban_info}"
|
||||
|
||||
# Формируем итоговое сообщение
|
||||
formatted_message = (
|
||||
f"👤 От: {author_info} | ID: {user_id}\n\n"
|
||||
f"📊 Постов в базе: {total_posts}\n"
|
||||
f"📝 Последний пост: {last_post_display}\n"
|
||||
f"📅 В боте с: {date_added}"
|
||||
f"{ban_section}\n\n"
|
||||
f"---\n"
|
||||
f"<b>Сообщение пользователя:</b>\n\n"
|
||||
f"<b>{safe_message_text}</b>"
|
||||
)
|
||||
|
||||
return formatted_message
|
||||
|
||||
|
||||
class PostService:
|
||||
"""Service for post-related operations"""
|
||||
@@ -236,6 +322,18 @@ class PostService:
|
||||
f"PostService: Ошибка сохранения скоров для {message_id}: {e}"
|
||||
)
|
||||
|
||||
async def _add_submitted_post_background(
|
||||
self, text: str, post_id: int, rag_score: float = None
|
||||
) -> None:
|
||||
"""Индексирует пост в RAG submitted collection в фоне."""
|
||||
try:
|
||||
if self.scoring_manager:
|
||||
await self.scoring_manager.add_submitted_post(text, post_id, rag_score)
|
||||
except Exception as e:
|
||||
logger.warning(
|
||||
f"PostService: Ошибка добавления поста в submitted: {e}"
|
||||
)
|
||||
|
||||
async def _get_scores_with_error_handling(self, text: str) -> tuple:
|
||||
"""
|
||||
Получает скоры для текста поста с обработкой ошибок.
|
||||
@@ -321,6 +419,37 @@ class PostService:
|
||||
error_message,
|
||||
) = await self._get_scores_with_error_handling(original_raw_text)
|
||||
|
||||
# Проверяем похожие посты (до добавления текущего в submitted)
|
||||
similar_warning = ""
|
||||
if self.scoring_manager and original_raw_text and original_raw_text.strip():
|
||||
try:
|
||||
similar_result = await self.scoring_manager.find_similar_posts(
|
||||
original_raw_text, threshold=0.9, hours=24
|
||||
)
|
||||
if similar_result and similar_result.similar_count > 0:
|
||||
# Формируем предупреждение с текстом похожего поста
|
||||
similar_text = ""
|
||||
if similar_result.similar_posts:
|
||||
first_similar = similar_result.similar_posts[0]
|
||||
if first_similar.text:
|
||||
truncated_text = first_similar.text[:150]
|
||||
if len(first_similar.text) > 150:
|
||||
truncated_text += "..."
|
||||
similar_text = f'\n<b>Текст поста:</b>\n"{html.escape(truncated_text)}"'
|
||||
|
||||
similar_warning = (
|
||||
f"\n\n⚠️ <b>Похожий пост за последние 24ч</b> "
|
||||
f"(совпадение {similar_result.max_similarity:.0%})"
|
||||
f"{similar_text}"
|
||||
)
|
||||
logger.info(
|
||||
f"PostService: Найден похожий пост для message_id={message.message_id}, "
|
||||
f"similar_count={similar_result.similar_count}, "
|
||||
f"max_similarity={similar_result.max_similarity:.2%}"
|
||||
)
|
||||
except Exception as e:
|
||||
logger.warning(f"PostService: Ошибка поиска похожих постов: {e}")
|
||||
|
||||
# Формируем текст для поста (с сообщением об ошибке если есть)
|
||||
text_for_post = original_raw_text
|
||||
if error_message:
|
||||
@@ -347,9 +476,11 @@ class PostService:
|
||||
message.from_user.username,
|
||||
deepseek_score=deepseek_score,
|
||||
rag_score=rag_score,
|
||||
rag_confidence=rag_confidence,
|
||||
rag_score_pos_only=rag_score_pos_only,
|
||||
user_id=message.from_user.id,
|
||||
)
|
||||
# Добавляем предупреждение о похожем посте
|
||||
if similar_warning:
|
||||
post_text += similar_warning
|
||||
|
||||
# Определяем анонимность по исходному тексту (без сообщения об ошибке)
|
||||
is_anonymous = determine_anonymity(original_raw_text)
|
||||
@@ -401,8 +532,11 @@ class PostService:
|
||||
markup,
|
||||
)
|
||||
elif content_type == "media_group":
|
||||
# Добавляем предупреждение о похожем посте в caption медиагруппы
|
||||
if similar_warning:
|
||||
post_text += similar_warning
|
||||
# Для медиагруппы используем специальную обработку
|
||||
# Передаем ml_scores_json для сохранения в БД
|
||||
# Передаем ml_scores_json и rag_score для сохранения в БД
|
||||
await self._process_media_group_background(
|
||||
message,
|
||||
album,
|
||||
@@ -411,6 +545,7 @@ class PostService:
|
||||
is_anonymous,
|
||||
original_raw_text,
|
||||
ml_scores_json,
|
||||
rag_score,
|
||||
)
|
||||
return
|
||||
else:
|
||||
@@ -448,6 +583,14 @@ class PostService:
|
||||
)
|
||||
)
|
||||
|
||||
# Индексируем пост в RAG submitted collection (после успешной отправки)
|
||||
if self.scoring_manager and original_raw_text and original_raw_text.strip():
|
||||
asyncio.create_task(
|
||||
self._add_submitted_post_background(
|
||||
original_raw_text, sent_message.message_id, rag_score
|
||||
)
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(
|
||||
f"PostService: Критическая ошибка в _process_post_background для {content_type}: {e}"
|
||||
@@ -462,6 +605,7 @@ class PostService:
|
||||
is_anonymous: bool,
|
||||
original_raw_text: str,
|
||||
ml_scores_json: str = None,
|
||||
rag_score: float = None,
|
||||
) -> None:
|
||||
"""Обрабатывает медиагруппу в фоне"""
|
||||
try:
|
||||
@@ -495,6 +639,14 @@ class PostService:
|
||||
self._save_scores_background(main_post_id, ml_scores_json)
|
||||
)
|
||||
|
||||
# Индексируем пост в RAG submitted collection
|
||||
if self.scoring_manager and original_raw_text and original_raw_text.strip():
|
||||
asyncio.create_task(
|
||||
self._add_submitted_post_background(
|
||||
original_raw_text, main_post_id, rag_score
|
||||
)
|
||||
)
|
||||
|
||||
for msg_id in media_group_message_ids:
|
||||
await self.db.add_message_link(main_post_id, msg_id)
|
||||
|
||||
@@ -552,8 +704,7 @@ class PostService:
|
||||
message.from_user.username,
|
||||
deepseek_score=deepseek_score,
|
||||
rag_score=rag_score,
|
||||
rag_confidence=rag_confidence,
|
||||
rag_score_pos_only=rag_score_pos_only,
|
||||
user_id=message.from_user.id,
|
||||
)
|
||||
markup = get_reply_keyboard_for_post()
|
||||
|
||||
@@ -611,8 +762,7 @@ class PostService:
|
||||
message.from_user.username,
|
||||
deepseek_score=deepseek_score,
|
||||
rag_score=rag_score,
|
||||
rag_confidence=rag_confidence,
|
||||
rag_score_pos_only=rag_score_pos_only,
|
||||
user_id=message.from_user.id,
|
||||
)
|
||||
|
||||
markup = get_reply_keyboard_for_post()
|
||||
@@ -677,8 +827,7 @@ class PostService:
|
||||
message.from_user.username,
|
||||
deepseek_score=deepseek_score,
|
||||
rag_score=rag_score,
|
||||
rag_confidence=rag_confidence,
|
||||
rag_score_pos_only=rag_score_pos_only,
|
||||
user_id=message.from_user.id,
|
||||
)
|
||||
|
||||
markup = get_reply_keyboard_for_post()
|
||||
@@ -770,8 +919,7 @@ class PostService:
|
||||
message.from_user.username,
|
||||
deepseek_score=deepseek_score,
|
||||
rag_score=rag_score,
|
||||
rag_confidence=rag_confidence,
|
||||
rag_score_pos_only=rag_score_pos_only,
|
||||
user_id=message.from_user.id,
|
||||
)
|
||||
|
||||
markup = get_reply_keyboard_for_post()
|
||||
@@ -869,8 +1017,7 @@ class PostService:
|
||||
message.from_user.username,
|
||||
deepseek_score=deepseek_score,
|
||||
rag_score=rag_score,
|
||||
rag_confidence=rag_confidence,
|
||||
rag_score_pos_only=rag_score_pos_only,
|
||||
user_id=message.from_user.id,
|
||||
)
|
||||
|
||||
is_anonymous = determine_anonymity(raw_caption)
|
||||
|
||||
Reference in New Issue
Block a user