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:
parent
049a152dc8
commit
9cee4e8d1f
4 changed files with 132 additions and 64 deletions
117
collect_all_members.py
Normal file
117
collect_all_members.py
Normal 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())
|
||||||
BIN
data/service_images/police_2a8a48fc857d4cf78a10583d6820f1be.png
Normal file
BIN
data/service_images/police_2a8a48fc857d4cf78a10583d6820f1be.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.8 MiB |
BIN
data/service_images/utility_508733a4ff614d18be9424936c45b57f.png
Normal file
BIN
data/service_images/utility_508733a4ff614d18be9424936c45b57f.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.6 MiB |
|
|
@ -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,62 +38,14 @@ 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,
|
user.username = message.from_user.username
|
||||||
)
|
user.first_name = message.from_user.first_name
|
||||||
session.add(user)
|
user.last_name = message.from_user.last_name
|
||||||
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.first_name = message.from_user.first_name
|
|
||||||
user.last_name = message.from_user.last_name
|
|
||||||
|
|
||||||
# Проверяем статус
|
# Проверяем статус
|
||||||
is_admin = user.is_admin or (user_id == config.ADMIN_USER_ID)
|
is_admin = user.is_admin or (user_id == config.ADMIN_USER_ID)
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue