✅ Регистрация и верификация жильцов ✅ Рейтинг активности (уровни) ✅ Детект шпионов (Score 0-100) ✅ Анти-мат система (3 предупреждения → бан) ✅ Админ-панель с рассылкой ✅ Учёт квартир (несколько жильцов) ✅ Телефоны экстренных служб и мастеров ✅ Еженедельный экспорт JSON ✅ Прокси (socks5) для обхода РКН Бекап: backups/versions/v1.0_mvp_2026-02-26/ Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
358 lines
12 KiB
Python
358 lines
12 KiB
Python
"""
|
||
Пользовательские команды
|
||
Доступны всем жильцам
|
||
"""
|
||
import logging
|
||
import json
|
||
from aiogram import Router, F
|
||
from aiogram.types import Message, CallbackQuery
|
||
from aiogram.filters import Command, CommandStart
|
||
from sqlalchemy.ext.asyncio import AsyncSession
|
||
from sqlalchemy import select
|
||
from database.db import AsyncSessionLocal
|
||
from database.models import User, Service, Announcement
|
||
from keyboards.inline import get_main_menu, get_phones_menu
|
||
from utils.formatters import format_phone_list
|
||
import config
|
||
|
||
logger = logging.getLogger(__name__)
|
||
|
||
router = Router()
|
||
|
||
|
||
@router.message(CommandStart())
|
||
async def cmd_start(message: Message):
|
||
"""Команда /start"""
|
||
user_id = message.from_user.id
|
||
|
||
async with AsyncSessionLocal() as session:
|
||
# Проверяем, есть ли пользователь в БД
|
||
stmt = select(User).where(User.user_id == user_id)
|
||
result = await session.execute(stmt)
|
||
user = result.scalar_one_or_none()
|
||
|
||
if not user:
|
||
# Создаём нового пользователя
|
||
user = User(
|
||
user_id=user_id,
|
||
username=message.from_user.username,
|
||
first_name=message.from_user.first_name,
|
||
last_name=message.from_user.last_name,
|
||
)
|
||
session.add(user)
|
||
await session.commit()
|
||
|
||
welcome_text = (
|
||
f'🏠 <b>Добро пожаловать, {message.from_user.first_name}!</b>\n\n'
|
||
f'Я — домовой бот нашего дома.\n\n'
|
||
f'❗ <b>Важно:</b> Для полного доступа пройдите верификацию.\n'
|
||
f'Отправьте мне номер вашей квартиры (например: <code>45</code> или <code>12А</code>)'
|
||
)
|
||
await message.answer(welcome_text, parse_mode='HTML')
|
||
else:
|
||
# Обновляем имя
|
||
user.username = message.from_user.username
|
||
user.first_name = message.from_user.first_name
|
||
user.last_name = message.from_user.last_name
|
||
await session.commit()
|
||
|
||
if user.verified:
|
||
await message.answer(
|
||
f'👋 Здравствуйте, {message.from_user.first_name}!\n\n'
|
||
f'Вы верифицированы (кв. {user.apartment}).\n'
|
||
f'Выберите действие:',
|
||
reply_markup=get_main_menu(),
|
||
parse_mode='HTML'
|
||
)
|
||
else:
|
||
await message.answer(
|
||
f'👋 Здравствуйте, {message.from_user.first_name}!\n\n'
|
||
f'⏳ Вы ещё не верифицированы.\n'
|
||
f'Отправьте номер квартиры для верификации.',
|
||
parse_mode='HTML'
|
||
)
|
||
|
||
|
||
@router.message(Command('помощь'))
|
||
@router.message(Command('help'))
|
||
async def cmd_help(message: Message):
|
||
"""Справка по командам"""
|
||
text = '''<b>📱 Команды бота:</b>
|
||
|
||
<b>Для всех жильцов:</b>
|
||
📞 /телефон — Экстренные службы
|
||
👷 /мастера — Сантехники, электрики
|
||
👮 /участковый — Контакты полиции
|
||
📅 /график — Отключения воды/света
|
||
📢 /объявления — Новости дома
|
||
🔍 /моя_квартира — Проверка привязки
|
||
|
||
<b>Верификация:</b>
|
||
Просто отправьте номер квартиры текстом
|
||
|
||
<b>Администратору:</b>
|
||
/admin — Панель админа
|
||
/stats — Статистика чата
|
||
/spies — Подозрительные
|
||
/export — Экспорт JSON
|
||
/users — Список жильцов'''
|
||
|
||
await message.answer(text, parse_mode='HTML', reply_markup=get_main_menu())
|
||
|
||
|
||
@router.callback_query(F.data == 'help')
|
||
async def cb_help(callback: CallbackQuery):
|
||
"""Справка (callback)"""
|
||
await callback.answer()
|
||
text = '''<b>🏠 Домовой бот — помощь</b>
|
||
|
||
Этот бот создан для помощи жильцам нашего дома.
|
||
|
||
<b>📞 Экстренные телефоны</b>
|
||
Быстрый доступ к телефонам служб
|
||
|
||
<b>👷 Мастера</b>
|
||
Проверенные сантехники и электрики
|
||
|
||
<b>🔍 Моя квартира</b>
|
||
Проверить привязку к квартире
|
||
|
||
<b>⏳ Верификация</b>
|
||
Отправьте номер квартиры текстом'''
|
||
|
||
await callback.message.answer(text, parse_mode='HTML')
|
||
|
||
|
||
@router.callback_query(F.data == 'phones')
|
||
async def cb_phones(callback: CallbackQuery):
|
||
"""Телефоны — главное меню"""
|
||
await callback.answer()
|
||
await callback.message.answer(
|
||
'📞 Выберите категорию:',
|
||
reply_markup=get_phones_menu()
|
||
)
|
||
|
||
|
||
@router.callback_query(F.data == 'phones_emergency')
|
||
async def cb_phones_emergency(callback: CallbackQuery):
|
||
"""Экстренные телефоны"""
|
||
await callback.answer()
|
||
|
||
async with AsyncSessionLocal() as session:
|
||
stmt = select(Service).where(Service.category == 'emergency').order_by(Service.name)
|
||
result = await session.execute(stmt)
|
||
services = list(result.scalars().all())
|
||
|
||
text = '<b>🚨 Экстренные службы</b>\n\n'
|
||
text += format_phone_list([
|
||
{
|
||
'name': s.name,
|
||
'phone': s.phone,
|
||
'description': s.description,
|
||
'category': s.category
|
||
}
|
||
for s in services
|
||
])
|
||
|
||
if not services:
|
||
text += 'Список пуст. Администратор, добавьте телефоны в БД.'
|
||
|
||
await callback.message.answer(text, parse_mode='HTML')
|
||
|
||
|
||
@router.callback_query(F.data == 'phones_utility')
|
||
async def cb_phones_utility(callback: CallbackQuery):
|
||
"""ЖКХ службы"""
|
||
await callback.answer()
|
||
|
||
async with AsyncSessionLocal() as session:
|
||
stmt = select(Service).where(Service.category == 'utility').order_by(Service.name)
|
||
result = await session.execute(stmt)
|
||
services = list(result.scalars().all())
|
||
|
||
text = '<b>🔧 Службы ЖКХ</b>\n\n'
|
||
text += format_phone_list([
|
||
{
|
||
'name': s.name,
|
||
'phone': s.phone,
|
||
'description': s.description,
|
||
'category': s.category
|
||
}
|
||
for s in services
|
||
])
|
||
|
||
await callback.message.answer(text, parse_mode='HTML')
|
||
|
||
|
||
@router.callback_query(F.data == 'phones_police')
|
||
async def cb_phones_police(callback: CallbackQuery):
|
||
"""Полиция"""
|
||
await callback.answer()
|
||
|
||
async with AsyncSessionLocal() as session:
|
||
stmt = select(Service).where(Service.category == 'police').order_by(Service.name)
|
||
result = await session.execute(stmt)
|
||
services = list(result.scalars().all())
|
||
|
||
text = '<b>👮 Полиция</b>\n\n'
|
||
text += format_phone_list([
|
||
{
|
||
'name': s.name,
|
||
'phone': s.phone,
|
||
'description': s.description,
|
||
'category': s.category
|
||
}
|
||
for s in services
|
||
])
|
||
|
||
await callback.message.answer(text, parse_mode='HTML')
|
||
|
||
|
||
@router.callback_query(F.data == 'masters')
|
||
async def cb_masters(callback: CallbackQuery):
|
||
"""Мастера"""
|
||
await callback.answer()
|
||
|
||
async with AsyncSessionLocal() as session:
|
||
stmt = select(Service).where(Service.category.in_(['masters', 'plumber', 'electrician'])).order_by(Service.name)
|
||
result = await session.execute(stmt)
|
||
services = list(result.scalars().all())
|
||
|
||
text = '<b>👷 Проверенные мастера</b>\n\n'
|
||
text += format_phone_list([
|
||
{
|
||
'name': s.name,
|
||
'phone': s.phone,
|
||
'description': s.description,
|
||
'category': s.category,
|
||
'rating': s.rating
|
||
}
|
||
for s in services
|
||
])
|
||
|
||
await callback.message.answer(text, parse_mode='HTML')
|
||
|
||
|
||
@router.callback_query(F.data == 'my_apartment')
|
||
async def cb_my_apartment(callback: CallbackQuery):
|
||
"""Моя квартира"""
|
||
await callback.answer()
|
||
|
||
async with AsyncSessionLocal() as session:
|
||
stmt = select(User).where(User.user_id == callback.from_user.id)
|
||
result = await session.execute(stmt)
|
||
user = result.scalar_one_or_none()
|
||
|
||
if not user:
|
||
text = '❌ Вы не зарегистрированы. Нажмите /start'
|
||
elif not user.apartment:
|
||
text = f'⏳ Вы не верифицированы.\n\nОтправьте номер квартиры текстом.'
|
||
else:
|
||
status = '✅' if user.verified else '⏳'
|
||
text = (
|
||
f'{status} <b>Ваша квартира: {user.apartment}</b>\n\n'
|
||
f'Статус: {"Верифицирован" if user.verified else "Не верифицирован"}\n'
|
||
f'Сообщений: {user.message_count}'
|
||
)
|
||
|
||
await callback.message.answer(text, parse_mode='HTML')
|
||
|
||
|
||
@router.callback_query(F.data == 'schedule')
|
||
async def cb_schedule(callback: CallbackQuery):
|
||
"""График отключений"""
|
||
await callback.answer()
|
||
|
||
# TODO: Загрузить реальный график
|
||
text = '''<b>📅 График отключений</b>
|
||
|
||
⚠️ Информация обновляется
|
||
|
||
Вода:
|
||
• Плановое отключение: каждое второе воскресенье месяца, 9:00-17:00
|
||
|
||
Электричество:
|
||
• Без плановых отключений
|
||
|
||
Отопление:
|
||
• Сезон: октябрь — апрель
|
||
|
||
Для актуальной информации обратитесь в УК.'''
|
||
|
||
await callback.message.answer(text, parse_mode='HTML')
|
||
|
||
|
||
@router.callback_query(F.data == 'announcements')
|
||
async def cb_announcements(callback: CallbackQuery):
|
||
"""Объявления"""
|
||
await callback.answer()
|
||
|
||
async with AsyncSessionLocal() as session:
|
||
stmt = (
|
||
select(Announcement)
|
||
.where(Announcement.is_active == True)
|
||
.order_by(Announcement.is_pinned.desc(), Announcement.created_at.desc())
|
||
.limit(10)
|
||
)
|
||
result = await session.execute(stmt)
|
||
announcements = list(result.scalars().all())
|
||
|
||
if not announcements:
|
||
await callback.message.answer('📭 Объявлений нет')
|
||
return
|
||
|
||
text = '<b>📢 Объявления</b>\n\n'
|
||
for ann in announcements:
|
||
pin = '📌 ' if ann.is_pinned else ''
|
||
text += f'{pin}<b>{ann.title}</b>\n{ann.text[:200]}...\n\n'
|
||
|
||
await callback.message.answer(text, parse_mode='HTML')
|
||
|
||
|
||
@router.callback_query(F.data == 'main_menu')
|
||
async def cb_main_menu(callback: CallbackQuery):
|
||
"""Главное меню"""
|
||
await callback.answer()
|
||
await callback.message.answer(
|
||
'🏠 <b>Главное меню</b>',
|
||
reply_markup=get_main_menu(),
|
||
parse_mode='HTML'
|
||
)
|
||
|
||
|
||
# Текстовые команды (алиасы)
|
||
@router.message(F.text.lower() == 'телефоны')
|
||
@router.message(F.text.lower() == 'телефон')
|
||
async def msg_phones_text(message: Message):
|
||
"""Текстовая команда телефоны"""
|
||
await message.answer(
|
||
'📞 Выберите категорию:',
|
||
reply_markup=get_phones_menu()
|
||
)
|
||
|
||
|
||
@router.message(F.text.lower() == 'мастера')
|
||
async def msg_masters_text(message: Message):
|
||
"""Текстовая команда мастера"""
|
||
await message.answer(
|
||
'👷 Загружаю список мастеров...',
|
||
reply_markup=get_main_menu()
|
||
)
|
||
# Перенаправляем на callback
|
||
async with AsyncSessionLocal() as session:
|
||
stmt = select(Service).where(Service.category.in_(['masters', 'plumber', 'electrician']))
|
||
result = await session.execute(stmt)
|
||
services = list(result.scalars().all())
|
||
|
||
text = '<b>👷 Проверенные мастера</b>\n\n'
|
||
text += format_phone_list([
|
||
{'name': s.name, 'phone': s.phone, 'description': s.description, 'rating': s.rating}
|
||
for s in services
|
||
])
|
||
await message.answer(text, parse_mode='HTML')
|
||
|
||
|
||
@router.message(F.text.lower() == 'помощь')
|
||
async def msg_help_text(message: Message):
|
||
"""Текстовая команда помощь"""
|
||
await cmd_help(message)
|