v1.22 Безопасность - белый список

🔒 БЕЗОПАСНОСТЬ:
• Бот работает ТОЛЬКО с пользователями из базы
• /start - игнор если нет в базе
• /help - игнор если нет в базе
• Верифицированные - полный доступ
• Неверифицированные - стимул пройти верификацию

📁 ФАЙЛЫ:
• handlers/users.py: проверка базы
• collect_all_members.py: скрипт сбора

🎯 СТИМУЛ:
• Хочешь бота? → Пройди верификацию!
• Админы, ИГ, верифицированные - работают
• Остальные - игнор

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
This commit is contained in:
Admin 2026-03-11 12:22:50 +00:00
parent 049a152dc8
commit 9cee4e8d1f
4 changed files with 132 additions and 64 deletions

117
collect_all_members.py Normal file
View file

@ -0,0 +1,117 @@
#!/usr/bin/env python3
"""
Сбор всех участников группы в базу данных
Использование: python collect_all_members.py
"""
import asyncio
import logging
import aiohttp
from aiohttp_socks import ProxyConnector
from sqlalchemy import select, func
from database.db import AsyncSessionLocal, init_db
from database.models import User
from config import BOT_TOKEN, ADMIN_CHAT_ID, get_proxy_url
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
async def collect_all_members():
"""Собрать всех участников группы"""
await init_db()
proxy_url = get_proxy_url()
connector = ProxyConnector.from_url(proxy_url)
async with aiohttp.ClientSession(connector=connector) as session:
# Получаем всех участников через getChatMembers
# Telegram API позволяет получать по 100 за раз
all_members = []
offset = 0
limit = 100
logger.info(f'🔄 Начинаем сбор участников...')
while True:
try:
url = f"https://api.telegram.org/bot{BOT_TOKEN}/getChatMembers"
params = {
"chat_id": ADMIN_CHAT_ID,
"offset": offset,
"limit": limit
}
async with session.get(url, params=params) as resp:
result = await resp.json()
if not result.get('ok'):
logger.error(f'Ошибка API: {result}')
break
members = result.get('result', [])
if not members:
break
all_members.extend(members)
offset += limit
logger.info(f'Загружено {len(all_members)} участников...')
# Если загрузили меньше чем лимит — это конец
if len(members) < limit:
break
except Exception as e:
logger.error(f'Ошибка при загрузке: {e}')
break
logger.info(f'Всего загружено: {len(all_members)}')
# Добавляем в базу
async with AsyncSessionLocal() as db_session:
added = 0
updated = 0
for member in all_members:
user_data = member.get('user', {})
user_id = user_data.get('id')
if not user_id:
continue
user = await db_session.get(User, user_id)
if not user:
# Новый пользователь
user = User(
user_id=user_id,
username=user_data.get('username'),
first_name=user_data.get('first_name'),
last_name=user_data.get('last_name'),
verified=False, # Пока не верифицирован
)
db_session.add(user)
added += 1
else:
# Обновляем данные
if user_data.get('username') and user.username != user_data.get('username'):
user.username = user_data.get('username')
updated += 1
if user_data.get('first_name') and user.first_name != user_data.get('first_name'):
user.first_name = user_data.get('first_name')
updated += 1
await db_session.commit()
# Статистика
total = (await db_session.execute(select(func.count(User.user_id)))).scalar()
logger.info(f'Сбор завершён!')
logger.info(f' Добавлено: {added}')
logger.info(f' Обновлено: {updated}')
logger.info(f' Всего в базе: {total}')
if __name__ == '__main__':
asyncio.run(collect_all_members())

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 MiB

View file

@ -24,15 +24,9 @@ router = Router()
async def cmd_start(message: Message): async def cmd_start(message: Message):
"""Команда /start (ТОЛЬКО В ЛИЧКЕ!)""" """Команда /start (ТОЛЬКО В ЛИЧКЕ!)"""
from utils.ui_styles import get_level_badge from utils.ui_styles import get_level_badge
from aiogram.types import ReplyKeyboardMarkup, KeyboardButton
# Проверяем что это личка # Проверяем что это личка
if message.chat.type != 'private': if message.chat.type != 'private':
await message.answer(
'💡 <b>Бот работает только в личных сообщениях!</b>\n\n'
'Нажмите на имя бота сверху и напишите /start в личке',
parse_mode='HTML'
)
return return
user_id = message.from_user.id user_id = message.from_user.id
@ -44,59 +38,11 @@ async def cmd_start(message: Message):
user = result.scalar_one_or_none() user = result.scalar_one_or_none()
if not user: if not user:
# Создаём нового пользователя # Пользователя нет в базе - ИГНОР!
user = User( logger.info(f'Пользователь {user_id} не в базе - игнорируем /start')
user_id=user_id, return
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()
# 🔴 НОВОЕ СООБЩЕНИЕ С КНОПКАМИ (БЕЗ <font>!) # Пользователь в базе - обновляем имя
welcome_text = f'''🔴 <b>ДОБРО ПОЖАЛОВАТЬ</b>
👋 <b>Привет, {message.from_user.first_name}!</b>
Я домовой бот нашего дома.
🏠 <b>Что я умею:</b>
📞 Телефоны экстренных служб
👷 Проверенные мастера
📅 График отключений
💰 Напоминания о платежах
🏆 Рейтинг и достижения
📋 Опросы и голосования
<b>ВАЖНО:</b>
Для полного доступа пройдите верификацию!
📝 <b>Как пройти верификацию:</b>
<b>Шаг 1:</b> Отправьте номер квартиры
Примеры: <code>45</code>, <code>12А</code>, <code>100-1</code>
<b>Шаг 2:</b> (опционально) Отправьте телефон
Пример: <code>+79991234567</code>
<b>Шаг 3:</b> Дождитесь подтверждения админа
После верификации откроются все функции! 🎉'''
# Кнопки для быстрого старта
kb = ReplyKeyboardMarkup(
keyboard=[
[KeyboardButton(text='📞 Экстренные телефоны')],
[KeyboardButton(text='📜 Правила чата'), KeyboardButton(text='❓ Помощь')],
],
resize_keyboard=True
)
await message.answer(welcome_text, parse_mode='HTML', reply_markup=kb)
else:
# Обновляем имя
user.username = message.from_user.username user.username = message.from_user.username
user.first_name = message.from_user.first_name user.first_name = message.from_user.first_name
user.last_name = message.from_user.last_name user.last_name = message.from_user.last_name
@ -182,15 +128,20 @@ async def cmd_start(message: Message):
@router.message(Command('help')) @router.message(Command('help'))
async def cmd_help(message: Message): async def cmd_help(message: Message):
"""Справка по командам (для жильцов)""" """Справка по командам (для жильцов)"""
from utils.ui_styles import get_colored_header, get_level_badge, get_user_level_with_ig from utils.ui_styles import get_colored_header, get_level_badge
from database.models import InitiativeGroup from database.models import InitiativeGroup
user_id = message.from_user.id user_id = message.from_user.id
async with AsyncSessionLocal() as session: async with AsyncSessionLocal() as session:
user = await session.get(User, user_id) user = await session.get(User, user_id)
# Проверяем есть ли в базе
if not user:
logger.info(f'Пользователь {user_id} не в базе - игнорируем /help')
return
# Определяем уровень # Определяем уровень
if user and user.verified: if user.verified:
ig_stmt = select(InitiativeGroup).where( ig_stmt = select(InitiativeGroup).where(
InitiativeGroup.user_id == user_id, InitiativeGroup.user_id == user_id,
InitiativeGroup.is_active == True InitiativeGroup.is_active == True