refactor
This commit is contained in:
228
bot/handlers/group_handlers.py
Normal file
228
bot/handlers/group_handlers.py
Normal file
@ -0,0 +1,228 @@
|
||||
"""Async handlers for group events."""
|
||||
from telebot.async_telebot import AsyncTeleBot
|
||||
from telebot import types
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy import select
|
||||
from bot.database import get_db_session, User, Chat, UserChat
|
||||
from bot.logger import get_logger
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
|
||||
def register_group_handlers(bot: AsyncTeleBot) -> None:
|
||||
"""Register all group event handlers."""
|
||||
|
||||
@bot.message_handler(content_types=['new_chat_members'])
|
||||
async def handle_new_member(message: types.Message) -> None:
|
||||
"""Handle bot being added to a chat."""
|
||||
# Check if bot was added
|
||||
bot_me = await bot.get_me()
|
||||
if not bot_me:
|
||||
return
|
||||
|
||||
for member in message.new_chat_members:
|
||||
if member.id == bot_me.id:
|
||||
chat_id = message.chat.id
|
||||
chat_title: str = message.chat.title or "Unknown"
|
||||
logger.info(f"Bot added to chat: {chat_title} (ID: {chat_id})")
|
||||
|
||||
async for db in get_db_session():
|
||||
try:
|
||||
# Check if chat exists
|
||||
result = await db.execute(select(Chat).filter(Chat.chat_id == chat_id))
|
||||
chat = result.scalar_one_or_none()
|
||||
if not chat:
|
||||
chat = Chat(chat_id=chat_id, chat_title=chat_title, bot_is_admin=False)
|
||||
db.add(chat)
|
||||
await db.commit()
|
||||
logger.info(f"Created new chat record: {chat_title} (ID: {chat_id})")
|
||||
|
||||
# Check if bot is admin
|
||||
try:
|
||||
bot_me = await bot.get_me()
|
||||
if not bot_me:
|
||||
return
|
||||
bot_member = await bot.get_chat_member(chat_id, bot_me.id)
|
||||
is_admin = bot_member.status in ['administrator', 'creator']
|
||||
chat.bot_is_admin = is_admin
|
||||
await db.commit()
|
||||
|
||||
if not is_admin:
|
||||
logger.warning(f"Bot is not admin in chat: {chat_title} (ID: {chat_id})")
|
||||
# Request admin rights
|
||||
await bot.reply_to(
|
||||
message,
|
||||
"Привет! Мне нужны права администратора, чтобы видеть список участников чата. "
|
||||
"Пожалуйста, выдайте мне права администратора."
|
||||
)
|
||||
else:
|
||||
logger.info(f"Bot is admin in chat: {chat_title} (ID: {chat_id})")
|
||||
# Sync users and show statistics
|
||||
await sync_chat_users(bot, db, chat_id)
|
||||
await show_statistics(bot, message.chat)
|
||||
except Exception as e:
|
||||
logger.error(f"Error checking admin status in chat {chat_id}: {e}", exc_info=True)
|
||||
# Bot might not have permission to check
|
||||
chat.bot_is_admin = False
|
||||
await db.commit()
|
||||
await bot.reply_to(
|
||||
message,
|
||||
"Привет! Мне нужны права администратора, чтобы видеть список участников чата. "
|
||||
"Пожалуйста, выдайте мне права администратора."
|
||||
)
|
||||
finally:
|
||||
await db.close()
|
||||
break
|
||||
|
||||
# NOTE:
|
||||
# Updates about the bot itself (когда бота повышают до админа / понижают)
|
||||
# приходят в поле `my_chat_member`, а не `chat_member`.
|
||||
# Для их обработки в pyTelegramBotAPI нужно использовать my_chat_member_handler.
|
||||
@bot.my_chat_member_handler()
|
||||
async def handle_chat_member_update(message: types.ChatMemberUpdated) -> None:
|
||||
"""Handle my_chat_member updates (bot role changes, e.g. becoming admin)."""
|
||||
bot_me = await bot.get_me()
|
||||
if not bot_me:
|
||||
return
|
||||
|
||||
if message.new_chat_member.user.id == bot_me.id:
|
||||
# Bot's status changed
|
||||
chat_id = message.chat.id
|
||||
chat_title: str = message.chat.title or "Unknown"
|
||||
new_status = message.new_chat_member.status
|
||||
logger.info(f"Bot status changed in chat {chat_title} (ID: {chat_id}): {new_status}")
|
||||
|
||||
async for db in get_db_session():
|
||||
try:
|
||||
result = await db.execute(select(Chat).filter(Chat.chat_id == chat_id))
|
||||
chat = result.scalar_one_or_none()
|
||||
if not chat:
|
||||
chat = Chat(chat_id=chat_id, chat_title=chat_title, bot_is_admin=False)
|
||||
db.add(chat)
|
||||
|
||||
# Check if bot became admin
|
||||
is_admin = message.new_chat_member.status in ['administrator', 'creator']
|
||||
was_admin = chat.bot_is_admin if chat else False
|
||||
if chat:
|
||||
chat.bot_is_admin = is_admin
|
||||
chat.chat_title = chat_title
|
||||
await db.commit()
|
||||
|
||||
if is_admin and not was_admin:
|
||||
logger.info(f"Bot promoted to admin in chat: {chat_title} (ID: {chat_id})")
|
||||
# Bot just became admin - sync users and show statistics
|
||||
await sync_chat_users(bot, db, chat_id)
|
||||
await show_statistics(bot, message.chat)
|
||||
elif not is_admin and was_admin:
|
||||
logger.warning(f"Bot demoted from admin in chat: {chat_title} (ID: {chat_id})")
|
||||
finally:
|
||||
await db.close()
|
||||
|
||||
|
||||
async def sync_chat_users(bot: AsyncTeleBot, db: AsyncSession, chat_id: int) -> None:
|
||||
"""Sync users from chat with database."""
|
||||
try:
|
||||
logger.debug(f"Syncing users for chat ID: {chat_id}")
|
||||
# Get all users who already shared birthday
|
||||
result = await db.execute(select(User))
|
||||
existing_users = result.scalars().all()
|
||||
logger.debug(f"Found {len(existing_users)} users with birthdays to sync")
|
||||
|
||||
synced_count = 0
|
||||
# Try to check if they're in this chat and add relationships
|
||||
for user in existing_users:
|
||||
try:
|
||||
member = await bot.get_chat_member(chat_id, user.user_id)
|
||||
if member.status not in ['left', 'kicked']:
|
||||
# User is in chat - ensure relationship exists
|
||||
user_chat_result = await db.execute(
|
||||
select(UserChat).filter(
|
||||
UserChat.user_id == user.user_id,
|
||||
UserChat.chat_id == chat_id
|
||||
)
|
||||
)
|
||||
user_chat = user_chat_result.scalar_one_or_none()
|
||||
|
||||
if not user_chat:
|
||||
user_chat = UserChat(user_id=user.user_id, chat_id=chat_id)
|
||||
db.add(user_chat)
|
||||
synced_count += 1
|
||||
except Exception as e:
|
||||
# User might have blocked bot or is not in chat
|
||||
logger.debug(f"Could not sync user {user.user_id} in chat {chat_id}: {e}")
|
||||
|
||||
await db.commit()
|
||||
logger.info(f"Synced {synced_count} users to chat ID: {chat_id}")
|
||||
except Exception as e:
|
||||
logger.error(f"Error syncing users for chat {chat_id}: {e}", exc_info=True)
|
||||
# If sync fails, continue anyway
|
||||
pass
|
||||
|
||||
|
||||
async def show_statistics(bot: AsyncTeleBot, chat: types.Chat) -> None:
|
||||
"""Show statistics about users who shared their birthday."""
|
||||
async for db in get_db_session():
|
||||
try:
|
||||
chat_id = chat.id
|
||||
logger.debug(f"Showing statistics for chat ID: {chat_id}")
|
||||
|
||||
# Get all chat members (exclude bots where possible)
|
||||
try:
|
||||
members_count_raw = await bot.get_chat_member_count(chat_id)
|
||||
# Try to subtract all bots (including this bot) using admin list
|
||||
human_members = members_count_raw
|
||||
try:
|
||||
admins = await bot.get_chat_administrators(chat_id)
|
||||
bots_in_admins = sum(1 for m in admins if getattr(m.user, "is_bot", False))
|
||||
human_members = max(members_count_raw - bots_in_admins, 0)
|
||||
except Exception:
|
||||
human_members = members_count_raw
|
||||
members_count = human_members
|
||||
except Exception as e:
|
||||
logger.warning(f"Could not get member count for chat {chat_id}: {e}")
|
||||
members_count = 0
|
||||
|
||||
# Get users from this chat who shared birthday
|
||||
from sqlalchemy import func
|
||||
result = await db.execute(
|
||||
select(func.count(User.user_id.distinct())).select_from(User).join(UserChat).filter(
|
||||
UserChat.chat_id == chat_id
|
||||
)
|
||||
)
|
||||
users_with_birthday = result.scalar() or 0
|
||||
|
||||
logger.info(f"Chat {chat_id} statistics: {users_with_birthday} users with birthdays out of {members_count} total")
|
||||
|
||||
# Create message
|
||||
if members_count > 0:
|
||||
percentage = (users_with_birthday / members_count) * 100
|
||||
message_text = (
|
||||
f"Отлично! Теперь я админ этого чата.\n\n"
|
||||
f"📊 Статистика:\n"
|
||||
f"• Поделились днем рождения: {users_with_birthday} из {members_count} участников\n"
|
||||
f"• Процент: {percentage:.1f}%"
|
||||
)
|
||||
else:
|
||||
message_text = (
|
||||
f"Отлично! Теперь я админ этого чата.\n\n"
|
||||
f"📊 Поделились днем рождения: {users_with_birthday} участников"
|
||||
)
|
||||
|
||||
# Create inline keyboard with button to start private chat
|
||||
bot_me = await bot.get_me()
|
||||
if not bot_me or not bot_me.username:
|
||||
await bot.send_message(chat_id, message_text)
|
||||
return
|
||||
|
||||
keyboard = types.InlineKeyboardMarkup()
|
||||
start_button = types.InlineKeyboardButton(
|
||||
text="Поделиться днем рождения",
|
||||
url=f"https://t.me/{bot_me.username}?start=share_birthday"
|
||||
)
|
||||
keyboard.add(start_button)
|
||||
|
||||
await bot.send_message(chat_id, message_text, reply_markup=keyboard)
|
||||
except Exception as e:
|
||||
logger.error(f"Error showing statistics for chat {chat_id}: {e}", exc_info=True)
|
||||
finally:
|
||||
await db.close()
|
||||
Reference in New Issue
Block a user