169 lines
7 KiB
Python
169 lines
7 KiB
Python
"""
|
||
Универсальный шлюз перенаправления в личные сообщения (Zero-Spam Redirect)
|
||
Все ответы на команды и меню отправляются строго в ЛС, не засоряя группу.
|
||
"""
|
||
import asyncio
|
||
import logging
|
||
import os
|
||
from aiogram.types import Message, CallbackQuery, InlineKeyboardMarkup, InlineKeyboardButton, FSInputFile
|
||
|
||
logger = logging.getLogger(__name__)
|
||
|
||
|
||
async def delete_after_delay(msg: Message, delay: int = 15):
|
||
"""Фоновое удаление временного уведомления через delay секунд"""
|
||
try:
|
||
await asyncio.sleep(delay)
|
||
await msg.delete()
|
||
except Exception:
|
||
pass
|
||
|
||
|
||
async def respond_pm_or_redirect(
|
||
event: Message | CallbackQuery,
|
||
text: str,
|
||
reply_markup=None,
|
||
photo_path: str = None,
|
||
parse_mode: str = 'HTML',
|
||
start_param: str = 'start'
|
||
):
|
||
"""
|
||
Отправляет контент пользователю строго в личные сообщения.
|
||
В групповом чате команда тихо удаляется, а пользователю отправляется карточка в ЛС.
|
||
Если бот не может написать в ЛС первым (пользователь не нажал /start),
|
||
выводится короткая временная плашка с кнопкой перехода в ЛС, удаляемая через 15 сек.
|
||
"""
|
||
bot = event.bot
|
||
|
||
# --- Обработка CallbackQuery ---
|
||
if isinstance(event, CallbackQuery):
|
||
message = event.message
|
||
|
||
# Если инлайн-кнопку нажали в группе
|
||
if message and message.chat.type in ('group', 'supergroup'):
|
||
user_id = event.from_user.id
|
||
try:
|
||
if photo_path and os.path.exists(photo_path):
|
||
await bot.send_photo(
|
||
chat_id=user_id,
|
||
photo=FSInputFile(photo_path),
|
||
caption=text,
|
||
reply_markup=reply_markup,
|
||
parse_mode=parse_mode
|
||
)
|
||
else:
|
||
await bot.send_message(
|
||
chat_id=user_id,
|
||
text=text,
|
||
reply_markup=reply_markup,
|
||
parse_mode=parse_mode
|
||
)
|
||
await event.answer("Отправил вам в личные сообщения 📩", show_alert=False)
|
||
except Exception as e:
|
||
logger.info(f"Cannot PM user {user_id} on callback: {e}")
|
||
await event.answer(
|
||
"Пожалуйста, сначала откройте диалог с ботом в личке (@lkmthreeseven_bot)!",
|
||
show_alert=True
|
||
)
|
||
return
|
||
|
||
# Если инлайн-кнопку нажали в ЛС
|
||
await event.answer()
|
||
has_media = False
|
||
if message:
|
||
has_media = bool(
|
||
(hasattr(message, 'photo') and message.photo) or
|
||
(hasattr(message, 'video') and message.video) or
|
||
(hasattr(message, 'document') and message.document) or
|
||
(hasattr(message, 'content_type') and message.content_type != 'text')
|
||
)
|
||
|
||
if has_media or (photo_path and os.path.exists(photo_path)):
|
||
if message:
|
||
try:
|
||
await message.delete()
|
||
except Exception:
|
||
pass
|
||
if photo_path and os.path.exists(photo_path):
|
||
await bot.send_photo(
|
||
chat_id=event.from_user.id,
|
||
photo=FSInputFile(photo_path),
|
||
caption=text,
|
||
reply_markup=reply_markup,
|
||
parse_mode=parse_mode
|
||
)
|
||
else:
|
||
await bot.send_message(
|
||
chat_id=event.from_user.id,
|
||
text=text,
|
||
reply_markup=reply_markup,
|
||
parse_mode=parse_mode
|
||
)
|
||
else:
|
||
try:
|
||
await message.edit_text(text, reply_markup=reply_markup, parse_mode=parse_mode)
|
||
except Exception:
|
||
await message.answer(text, reply_markup=reply_markup, parse_mode=parse_mode)
|
||
return
|
||
|
||
# --- Обработка Message ---
|
||
if isinstance(event, Message):
|
||
user_id = event.from_user.id
|
||
|
||
# Если сообщение получено в личке — штатный ответ
|
||
if event.chat.type == 'private':
|
||
if photo_path and os.path.exists(photo_path):
|
||
await event.answer_photo(
|
||
FSInputFile(photo_path),
|
||
caption=text,
|
||
reply_markup=reply_markup,
|
||
parse_mode=parse_mode
|
||
)
|
||
else:
|
||
await event.answer(text, reply_markup=reply_markup, parse_mode=parse_mode)
|
||
return
|
||
|
||
# Если сообщение получено в группе или супергруппе:
|
||
# 1. Пытаемся тихо удалить сообщение-команду жильца
|
||
try:
|
||
await event.delete()
|
||
except Exception:
|
||
pass
|
||
|
||
# 2. Отправляем карточку/меню пользователю в ЛС
|
||
sent_to_pm = False
|
||
try:
|
||
if photo_path and os.path.exists(photo_path):
|
||
await bot.send_photo(
|
||
chat_id=user_id,
|
||
photo=FSInputFile(photo_path),
|
||
caption=text,
|
||
reply_markup=reply_markup,
|
||
parse_mode=parse_mode
|
||
)
|
||
else:
|
||
await bot.send_message(
|
||
chat_id=user_id,
|
||
text=text,
|
||
reply_markup=reply_markup,
|
||
parse_mode=parse_mode
|
||
)
|
||
sent_to_pm = True
|
||
except Exception as e:
|
||
logger.info(f"Cannot PM user {user_id}: {e}")
|
||
|
||
# 3. Если в личку отправить не удалось (пользователь не начинал диалог с ботом)
|
||
if not sent_to_pm:
|
||
bot_user = await bot.get_me()
|
||
url = f"https://t.me/{bot_user.username}?start={start_param}"
|
||
kb = InlineKeyboardMarkup(inline_keyboard=[[
|
||
InlineKeyboardButton(text="💬 Открыть в личке", url=url)
|
||
]])
|
||
user_mention = event.from_user.mention_html()
|
||
hint = await bot.send_message(
|
||
chat_id=event.chat.id,
|
||
text=f"👋 {user_mention}, чтобы не спамить в общий чат, перейдите в диалог с ботом:",
|
||
reply_markup=kb,
|
||
parse_mode='HTML'
|
||
)
|
||
asyncio.create_task(delete_after_delay(hint, 15))
|