domovoy_bot/services/achievements.py
Admin ba4676b0af v1.0 MVP - Инициализация проекта 🏠
 Регистрация и верификация жильцов
 Рейтинг активности (уровни)
 Детект шпионов (Score 0-100)
 Анти-мат система (3 предупреждения → бан)
 Админ-панель с рассылкой
 Учёт квартир (несколько жильцов)
 Телефоны экстренных служб и мастеров
 Еженедельный экспорт JSON
 Прокси (socks5) для обхода РКН

Бекап: backups/versions/v1.0_mvp_2026-02-26/

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-02-27 10:54:16 +00:00

299 lines
12 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
Achievements Service — система достижений пользователей
"""
import logging
import json
from datetime import datetime
from typing import Dict, List, Optional, Tuple
from sqlalchemy import select, func
from sqlalchemy.ext.asyncio import AsyncSession
logger = logging.getLogger(__name__)
class AchievementsService:
"""Сервис достижений"""
def __init__(self, session: AsyncSession):
self.session = session
self.achievements = self._get_achievements_config()
def _get_achievements_config(self) -> Dict:
"""Конфигурация достижений"""
return {
'newbie': {
'name': 'Новичок',
'emoji': '🌱',
'description': 'Первое сообщение в чате',
'category': 'activity'
},
'active_10': {
'name': 'Активист 10',
'emoji': '📝',
'description': '10 сообщений в чате',
'category': 'activity'
},
'active_100': {
'name': 'Активист 100',
'emoji': '✍️',
'description': '100 сообщений в чате',
'category': 'activity'
},
'active_1000': {
'name': 'Говорун 1000',
'emoji': '🗣️',
'description': '1000 сообщений в чате',
'category': 'activity'
},
'verified': {
'name': 'Свой человек',
'emoji': '',
'description': 'Пройдена верификация',
'category': 'verification'
},
'rating_100': {
'name': 'Сосед',
'emoji': '🏠',
'description': 'Рейтинг 100 баллов',
'category': 'rating'
},
'rating_500': {
'name': 'Активист',
'emoji': '',
'description': 'Рейтинг 500 баллов',
'category': 'rating'
},
'rating_1000': {
'name': 'Лидер',
'emoji': '👑',
'description': 'Рейтинг 1000 баллов',
'category': 'rating'
},
'early_bird': {
'name': 'Жаворонок',
'emoji': '🐦',
'description': 'Сообщение в 6-7 утра',
'category': 'time'
},
'night_owl': {
'name': 'Сова',
'emoji': '🦉',
'description': 'Сообщение после 23:00',
'category': 'time'
},
'helper': {
'name': 'Помощник',
'emoji': '🤝',
'description': 'Получил благодарность',
'category': 'social'
},
'spy_hunter': {
'name': 'Охотник на шпионов',
'emoji': '🕵️',
'description': 'Сообщение о подозрительном поведении',
'category': 'security'
},
'week_warrior': {
'name': 'Недельный воин',
'emoji': '📅',
'description': 'Активность 7 дней подряд',
'category': 'streak'
},
'month_master': {
'name': 'Месячный мастер',
'emoji': '🗓️',
'description': 'Активность 30 дней подряд',
'category': 'streak'
},
'poll_participant': {
'name': 'Участник опроса',
'emoji': '📊',
'description': 'Участие в опросе',
'category': 'polls'
},
'ad_publisher': {
'name': 'Объявитель',
'emoji': '📢',
'description': 'Публикация объявления',
'category': 'ads'
},
'helper': {
'name': 'Помощник',
'emoji': '🤝',
'description': 'Получил благодарность',
'category': 'social'
},
}
async def check_and_award(self, user_id: int, **kwargs) -> List[str]:
"""
Проверить и выдать достижения
Returns: список полученных достижений
"""
from database.models import Achievement, User
awarded = []
user = await self.session.get(User, user_id)
if not user:
return []
# Получаем текущие достижения пользователя
stmt = select(Achievement.achievement_type).where(Achievement.user_id == user_id)
result = await self.session.execute(stmt)
existing = set(row[0] for row in result.all())
# Проверка по сообщениям
if 'message_count' in kwargs:
msg_count = kwargs['message_count']
if msg_count >= 1 and 'newbie' not in existing:
await self._award(user_id, 'newbie')
awarded.append('newbie')
if msg_count >= 10 and 'active_10' not in existing:
await self._award(user_id, 'active_10')
awarded.append('active_10')
if msg_count >= 100 and 'active_100' not in existing:
await self._award(user_id, 'active_100')
awarded.append('active_100')
if msg_count >= 1000 and 'active_1000' not in existing:
await self._award(user_id, 'active_1000')
awarded.append('active_1000')
# Проверка по верификации
if 'verified' in kwargs and kwargs['verified'] and 'verified' not in existing:
await self._award(user_id, 'verified')
awarded.append('verified')
# Проверка по рейтингу
if 'rating' in kwargs:
rating = kwargs['rating']
if rating >= 100 and 'rating_100' not in existing:
await self._award(user_id, 'rating_100')
awarded.append('rating_100')
if rating >= 500 and 'rating_500' not in existing:
await self._award(user_id, 'rating_500')
awarded.append('rating_500')
if rating >= 1000 and 'rating_1000' not in existing:
await self._award(user_id, 'rating_1000')
awarded.append('rating_1000')
# Проверка по времени сообщения
if 'message_hour' in kwargs:
hour = kwargs['message_hour']
if 6 <= hour < 7 and 'early_bird' not in existing:
await self._award(user_id, 'early_bird')
awarded.append('early_bird')
if hour >= 23 and 'night_owl' not in existing:
await self._award(user_id, 'night_owl')
awarded.append('night_owl')
# Проверка по участию в опросе
if 'poll_vote' in kwargs and kwargs['poll_vote'] and 'poll_participant' not in existing:
await self._award(user_id, 'poll_participant')
awarded.append('poll_participant')
# Проверка по публикации объявления
if 'ad_published' in kwargs and kwargs['ad_published'] and 'ad_publisher' not in existing:
await self._award(user_id, 'ad_publisher')
awarded.append('ad_publisher')
# Проверка по благодарности (помощник)
if 'helper' in kwargs and kwargs['helper'] and 'helper' not in existing:
await self._award(user_id, 'helper')
awarded.append('helper')
return awarded
async def _award(self, user_id: int, achievement_type: str, metadata: dict = None):
"""Выдать достижение"""
from database.models import Achievement
achievement = Achievement(
user_id=user_id,
achievement_type=achievement_type,
metadata=json.dumps(metadata) if metadata else None
)
self.session.add(achievement)
await self.session.commit()
# Уведомление пользователя
try:
from aiogram import Bot
from config import ADMIN_USER_ID
ach_data = self.achievements.get(achievement_type, {})
# Бот будет передан извне
# await bot.send_message(user_id, f"🏆 {ach_data.get('emoji', '🏆')} {ach_data.get('name', 'Достижение')}!\n\n{ach_data.get('description', '')}")
logger.info(f"Достижение {achievement_type} выдано пользователю {user_id}")
except Exception as e:
logger.error(f"Ошибка уведомления о достижении: {e}")
async def get_user_achievements(self, user_id: int) -> List[Dict]:
"""Получить достижения пользователя"""
from database.models import Achievement
stmt = select(Achievement).where(Achievement.user_id == user_id).order_by(Achievement.earned_at)
result = await self.session.execute(stmt)
achievements = list(result.scalars().all())
result = []
for ach in achievements:
ach_data = self.achievements.get(ach.achievement_type, {})
result.append({
'type': ach.achievement_type,
'name': ach_data.get('name', ach.achievement_type),
'emoji': ach_data.get('emoji', '🏆'),
'description': ach_data.get('description', ''),
'category': ach_data.get('category', 'other'),
'earned_at': ach.earned_at.isoformat()
})
return result
async def get_all_achievements(self) -> Dict:
"""Получить все доступные достижения"""
return self.achievements
async def get_leaderboard(self, limit: int = 10) -> List[Dict]:
"""Получить топ пользователей по достижениям"""
from database.models import Achievement, User
stmt = (
select(
User.user_id,
User.full_name,
User.apartment,
func.count(Achievement.id).label('achievement_count')
)
.join(Achievement, User.user_id == Achievement.user_id, isouter=True)
.group_by(User.user_id)
.order_by(func.count(Achievement.id).desc())
.limit(limit)
)
result = await self.session.execute(stmt)
rows = list(result.all())
return [
{
'user_id': row.user_id,
'name': row.full_name or f'user{row.user_id}',
'apartment': row.apartment or '?',
'achievements': row.achievement_count
}
for row in rows
]
def get_achievement_info(self, achievement_type: str) -> Dict:
"""Получить информацию о достижении"""
return self.achievements.get(achievement_type, {})