✅ Регистрация и верификация жильцов ✅ Рейтинг активности (уровни) ✅ Детект шпионов (Score 0-100) ✅ Анти-мат система (3 предупреждения → бан) ✅ Админ-панель с рассылкой ✅ Учёт квартир (несколько жильцов) ✅ Телефоны экстренных служб и мастеров ✅ Еженедельный экспорт JSON ✅ Прокси (socks5) для обхода РКН Бекап: backups/versions/v1.0_mvp_2026-02-26/ Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
297 lines
10 KiB
Python
297 lines
10 KiB
Python
"""
|
||
Управление телефонами пользователей
|
||
"""
|
||
import logging
|
||
import re
|
||
from aiogram import Router, F
|
||
from aiogram.types import Message
|
||
from aiogram.filters import Command
|
||
from sqlalchemy.ext.asyncio import AsyncSession
|
||
from sqlalchemy import select
|
||
from database.db import AsyncSessionLocal
|
||
from database.models import User
|
||
from utils.decorators import admin_only
|
||
from utils.validators import validate_phone, normalize_phone
|
||
|
||
logger = logging.getLogger(__name__)
|
||
|
||
router = Router()
|
||
|
||
|
||
def format_phone_display(phone: str) -> str:
|
||
"""Форматирование телефона для отображения"""
|
||
if not phone:
|
||
return 'не указан'
|
||
|
||
# Добавляем кликабельную ссылку tel:
|
||
cleaned = re.sub(r'[^\d+]', '', phone)
|
||
return f'<a href="tel:{cleaned}">{phone}</a>'
|
||
|
||
|
||
@router.message(Command('add_phone'))
|
||
@admin_only
|
||
async def cmd_add_phone(message: Message):
|
||
"""
|
||
Добавить телефон пользователю
|
||
Использование: /add_phone <user_id> <телефон>
|
||
"""
|
||
args = message.text.split(maxsplit=2)
|
||
|
||
if len(args) < 3:
|
||
await message.answer(
|
||
'Использование: <code>/add_phone <user_id> <телефон></code>\n\n'
|
||
'Пример: <code>/add_phone 123456789 +7 (999) 123-45-67</code>',
|
||
parse_mode='HTML'
|
||
)
|
||
return
|
||
|
||
try:
|
||
user_id = int(args[1])
|
||
except ValueError:
|
||
await message.answer('❌ Неверный ID пользователя')
|
||
return
|
||
|
||
phone = args[2].strip()
|
||
|
||
if not validate_phone(phone):
|
||
await message.answer('❌ Неверный формат телефона\n\nПример: +7 (999) 123-45-67')
|
||
return
|
||
|
||
# Нормализуем телефон
|
||
phone = normalize_phone(phone)
|
||
|
||
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:
|
||
await message.answer('❌ Пользователь не найден')
|
||
return
|
||
|
||
# Добавляем телефон
|
||
user.phone = phone
|
||
await session.commit()
|
||
|
||
logger.info(f'Добавлен телефон пользователю {user_id}: {phone}')
|
||
|
||
await message.answer(
|
||
f'✅ <b>Телефон добавлен</b>\n\n'
|
||
f'Пользователь: {user.full_name}\n'
|
||
f'ID: <code>{user_id}</code>\n'
|
||
f'Телефон: <code>{phone}</code>\n\n'
|
||
f'💡 Теперь можно позвонить кликом!',
|
||
parse_mode='HTML'
|
||
)
|
||
|
||
|
||
@router.message(Command('edit_phone'))
|
||
@admin_only
|
||
async def cmd_edit_phone(message: Message):
|
||
"""
|
||
Изменить телефон пользователю
|
||
Использование: /edit_phone <user_id> <телефон>
|
||
"""
|
||
args = message.text.split(maxsplit=2)
|
||
|
||
if len(args) < 3:
|
||
await message.answer(
|
||
'Использование: <code>/edit_phone <user_id> <телефон></code>\n\n'
|
||
'Пример: <code>/edit_phone 123456789 +7 (999) 999-99-99</code>',
|
||
parse_mode='HTML'
|
||
)
|
||
return
|
||
|
||
try:
|
||
user_id = int(args[1])
|
||
except ValueError:
|
||
await message.answer('❌ Неверный ID пользователя')
|
||
return
|
||
|
||
phone = args[2].strip()
|
||
|
||
if not validate_phone(phone):
|
||
await message.answer('❌ Неверный формат телефона')
|
||
return
|
||
|
||
phone = normalize_phone(phone)
|
||
|
||
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:
|
||
await message.answer('❌ Пользователь не найден')
|
||
return
|
||
|
||
old_phone = user.phone
|
||
user.phone = phone
|
||
await session.commit()
|
||
|
||
logger.info(f'Изменён телефон пользователю {user_id}: {old_phone} → {phone}')
|
||
|
||
await message.answer(
|
||
f'✅ <b>Телефон изменён</b>\n\n'
|
||
f'Пользователь: {user.full_name}\n'
|
||
f'ID: <code>{user_id}</code>\n'
|
||
f'Было: <code>{old_phone or "не указан"}</code>\n'
|
||
f'Стало: <code>{phone}</code>',
|
||
parse_mode='HTML'
|
||
)
|
||
|
||
|
||
@router.message(Command('remove_phone'))
|
||
@router.message(Command('del_phone'))
|
||
@admin_only
|
||
async def cmd_remove_phone(message: Message):
|
||
"""
|
||
Удалить телефон у пользователя
|
||
Использование: /remove_phone <user_id>
|
||
"""
|
||
args = message.text.split()
|
||
|
||
if len(args) < 2:
|
||
await message.answer(
|
||
'Использование: <code>/remove_phone <user_id></code>\n\n'
|
||
'Пример: <code>/remove_phone 123456789</code>',
|
||
parse_mode='HTML'
|
||
)
|
||
return
|
||
|
||
try:
|
||
user_id = int(args[1])
|
||
except ValueError:
|
||
await message.answer('❌ Неверный ID пользователя')
|
||
return
|
||
|
||
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:
|
||
await message.answer('❌ Пользователь не найден')
|
||
return
|
||
|
||
old_phone = user.phone
|
||
user.phone = None
|
||
await session.commit()
|
||
|
||
logger.info(f'Удалён телефон пользователю {user_id}: {old_phone}')
|
||
|
||
await message.answer(
|
||
f'✅ <b>Телефон удалён</b>\n\n'
|
||
f'Пользователь: {user.full_name}\n'
|
||
f'ID: <code>{user_id}</code>\n'
|
||
f'Был: <code>{old_phone or "не указан"}</code>',
|
||
parse_mode='HTML'
|
||
)
|
||
|
||
|
||
@router.message(Command('myphone'))
|
||
async def cmd_myphone(message: Message):
|
||
"""
|
||
Добавить/изменить свой телефон (для жильцов)
|
||
Использование: /myphone +7 (999) 123-45-67
|
||
"""
|
||
args = message.text.split(maxsplit=1)
|
||
|
||
if len(args) < 2:
|
||
# Показываем текущий телефон
|
||
async with AsyncSessionLocal() as session:
|
||
stmt = select(User).where(User.user_id == message.from_user.id)
|
||
result = await session.execute(stmt)
|
||
user = result.scalar_one_or_none()
|
||
|
||
if user and user.phone:
|
||
await message.answer(
|
||
f'📱 <b>Ваш телефон</b>\n\n'
|
||
f'<code>{user.phone}</code>\n\n'
|
||
f'Чтобы изменить: <code>/myphone +7 (999) 123-45-67</code>',
|
||
parse_mode='HTML'
|
||
)
|
||
else:
|
||
await message.answer(
|
||
'📱 <b>Ваш телефон не указан</b>\n\n'
|
||
'Добавьте телефон командой:\n'
|
||
'<code>/myphone +7 (999) 123-45-67</code>\n\n'
|
||
'Это поможет соседям связаться с вами!',
|
||
parse_mode='HTML'
|
||
)
|
||
return
|
||
|
||
phone = args[1].strip()
|
||
|
||
if not validate_phone(phone):
|
||
await message.answer('❌ Неверный формат телефона\n\nПример: +7 (999) 123-45-67')
|
||
return
|
||
|
||
phone = normalize_phone(phone)
|
||
|
||
async with AsyncSessionLocal() as session:
|
||
stmt = select(User).where(User.user_id == message.from_user.id)
|
||
result = await session.execute(stmt)
|
||
user = result.scalar_one_or_none()
|
||
|
||
if not user:
|
||
await message.answer('❌ Вы не зарегистрированы. Нажмите /start')
|
||
return
|
||
|
||
old_phone = user.phone
|
||
user.phone = phone
|
||
await session.commit()
|
||
|
||
logger.info(f'Пользователь {message.from_user.id} установил телефон: {phone}')
|
||
|
||
await message.answer(
|
||
f'✅ <b>Телефон сохранён</b>\n\n'
|
||
f'<code>{phone}</code>\n\n'
|
||
f'Теперь соседи могут связаться с вами!',
|
||
parse_mode='HTML'
|
||
)
|
||
|
||
|
||
@router.message(Command('phone'))
|
||
@admin_only
|
||
async def cmd_phone_info(message: Message):
|
||
"""
|
||
Показать телефон пользователя
|
||
Использование: /phone <user_id>
|
||
"""
|
||
args = message.text.split()
|
||
|
||
if len(args) < 2:
|
||
await message.answer(
|
||
'Использование: <code>/phone <user_id></code>\n\n'
|
||
'Пример: <code>/phone 123456789</code>',
|
||
parse_mode='HTML'
|
||
)
|
||
return
|
||
|
||
try:
|
||
user_id = int(args[1])
|
||
except ValueError:
|
||
await message.answer('❌ Неверный ID пользователя')
|
||
return
|
||
|
||
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:
|
||
await message.answer('❌ Пользователь не найден')
|
||
return
|
||
|
||
phone_text = format_phone_display(user.phone)
|
||
|
||
await message.answer(
|
||
f'📱 <b>Телефон</b>\n\n'
|
||
f'Пользователь: {user.full_name}\n'
|
||
f'Квартира: {user.apartment or "не указана"}\n'
|
||
f'Телефон: {phone_text}\n\n'
|
||
f'💡 Нажмите на номер чтобы позвонить!',
|
||
parse_mode='HTML'
|
||
)
|