Merge pull request #13 from yazhog/codex/find-message-handling-functions-in-bot
Send referral messages as images
This commit is contained in:
+4
-1
@@ -190,11 +190,14 @@ CRYPTOBOT_INVOICE_EXPIRES_HOURS=24
|
||||
|
||||
# ===== ИНТЕРФЕЙС И UX =====
|
||||
|
||||
# Включить логотип для всех сообщений (true - с изображением, false - только текст)
|
||||
ENABLE_LOGO_MODE=true
|
||||
LOGO_FILE=vpn_logo.png
|
||||
|
||||
# Режим работы кнопки "Подключиться"
|
||||
# guide - открывает гайд подключения (режим 1)
|
||||
# miniapp_subscription - открывает ссылку подписки в мини-приложении (режим 2)
|
||||
# miniapp_custom - открывает заданную ссылку в мини-приложении (режим 3)
|
||||
# link - открывает ссылку подписки напрямую (режим 4)
|
||||
CONNECT_BUTTON_MODE=guide
|
||||
|
||||
# URL для режима miniapp_custom (обязателен при CONNECT_BUTTON_MODE=miniapp_custom)
|
||||
|
||||
@@ -30,6 +30,9 @@ from app.handlers.admin import (
|
||||
)
|
||||
from app.handlers.stars_payments import register_stars_handlers
|
||||
|
||||
from app.utils.message_patch import patch_message_methods
|
||||
|
||||
patch_message_methods()
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -138,6 +138,8 @@ class Settings(BaseSettings):
|
||||
|
||||
CONNECT_BUTTON_MODE: str = "guide"
|
||||
MINIAPP_CUSTOM_URL: str = ""
|
||||
ENABLE_LOGO_MODE: bool = True
|
||||
LOGO_FILE: str = "vpn_logo.png"
|
||||
|
||||
DEFAULT_LANGUAGE: str = "ru"
|
||||
AVAILABLE_LANGUAGES: str = "ru,en"
|
||||
|
||||
+44
-46
@@ -11,6 +11,7 @@ from app.config import settings
|
||||
from app.database.models import User
|
||||
from app.keyboards.inline import get_referral_keyboard
|
||||
from app.localization.texts import get_texts
|
||||
from app.utils.photo_message import edit_or_answer_photo
|
||||
from app.utils.user_utils import (
|
||||
get_detailed_referral_list,
|
||||
get_referral_analytics,
|
||||
@@ -90,20 +91,12 @@ async def show_referral_info(
|
||||
referral_text += "\n"
|
||||
|
||||
referral_text += "📢 Приглашайте друзей и зарабатывайте!"
|
||||
|
||||
if callback.message.text:
|
||||
await callback.message.edit_text(
|
||||
referral_text,
|
||||
reply_markup=get_referral_keyboard(db_user.language),
|
||||
parse_mode="HTML"
|
||||
)
|
||||
else:
|
||||
await callback.message.delete()
|
||||
await callback.message.answer(
|
||||
referral_text,
|
||||
reply_markup=get_referral_keyboard(db_user.language),
|
||||
parse_mode="HTML"
|
||||
)
|
||||
|
||||
await edit_or_answer_photo(
|
||||
callback,
|
||||
referral_text,
|
||||
get_referral_keyboard(db_user.language),
|
||||
)
|
||||
await callback.answer()
|
||||
|
||||
|
||||
@@ -111,6 +104,8 @@ async def show_referral_qr(
|
||||
callback: types.CallbackQuery,
|
||||
db_user: User,
|
||||
):
|
||||
await callback.answer()
|
||||
|
||||
texts = get_texts(db_user.language)
|
||||
|
||||
bot_username = (await callback.bot.get_me()).username
|
||||
@@ -144,7 +139,6 @@ async def show_referral_qr(
|
||||
caption=f"🔗 Ваша реферальная ссылка:\n{referral_link}",
|
||||
reply_markup=keyboard,
|
||||
)
|
||||
await callback.answer()
|
||||
|
||||
|
||||
async def show_detailed_referral_list(
|
||||
@@ -154,15 +148,17 @@ async def show_detailed_referral_list(
|
||||
page: int = 1
|
||||
):
|
||||
texts = get_texts(db_user.language)
|
||||
|
||||
|
||||
referrals_data = await get_detailed_referral_list(db, db_user.id, limit=10, offset=(page - 1) * 10)
|
||||
|
||||
|
||||
if not referrals_data['referrals']:
|
||||
await callback.message.edit_text(
|
||||
await edit_or_answer_photo(
|
||||
callback,
|
||||
"📋 У вас пока нет рефералов.\n\nПоделитесь своей реферальной ссылкой, чтобы начать зарабатывать!",
|
||||
reply_markup=types.InlineKeyboardMarkup(inline_keyboard=[
|
||||
[types.InlineKeyboardButton(text=texts.BACK, callback_data="menu_referrals")]
|
||||
])
|
||||
types.InlineKeyboardMarkup(
|
||||
inline_keyboard=[[types.InlineKeyboardButton(text=texts.BACK, callback_data="menu_referrals")]]
|
||||
),
|
||||
parse_mode=None,
|
||||
)
|
||||
await callback.answer()
|
||||
return
|
||||
@@ -205,14 +201,14 @@ async def show_detailed_referral_list(
|
||||
keyboard.append(nav_buttons)
|
||||
|
||||
keyboard.append([types.InlineKeyboardButton(
|
||||
text=texts.BACK,
|
||||
text=texts.BACK,
|
||||
callback_data="menu_referrals"
|
||||
)])
|
||||
|
||||
await callback.message.edit_text(
|
||||
|
||||
await edit_or_answer_photo(
|
||||
callback,
|
||||
text,
|
||||
reply_markup=types.InlineKeyboardMarkup(inline_keyboard=keyboard),
|
||||
parse_mode="HTML"
|
||||
types.InlineKeyboardMarkup(inline_keyboard=keyboard),
|
||||
)
|
||||
await callback.answer()
|
||||
|
||||
@@ -223,31 +219,31 @@ async def show_referral_analytics(
|
||||
db: AsyncSession
|
||||
):
|
||||
texts = get_texts(db_user.language)
|
||||
|
||||
|
||||
analytics = await get_referral_analytics(db, db_user.id)
|
||||
|
||||
|
||||
text = f"📊 <b>Аналитика рефералов</b>\n\n"
|
||||
|
||||
|
||||
text += f"💰 <b>Доходы по периодам:</b>\n"
|
||||
text += f"• Сегодня: {texts.format_price(analytics['earnings_by_period']['today'])}\n"
|
||||
text += f"• За неделю: {texts.format_price(analytics['earnings_by_period']['week'])}\n"
|
||||
text += f"• За месяц: {texts.format_price(analytics['earnings_by_period']['month'])}\n"
|
||||
text += f"• За квартал: {texts.format_price(analytics['earnings_by_period']['quarter'])}\n\n"
|
||||
|
||||
|
||||
if analytics['top_referrals']:
|
||||
text += f"🏆 <b>Топ-{len(analytics['top_referrals'])} рефералов:</b>\n"
|
||||
for i, ref in enumerate(analytics['top_referrals'], 1):
|
||||
text += f"{i}. {ref['referral_name']}: {texts.format_price(ref['total_earned_kopeks'])} ({ref['earnings_count']} начислений)\n"
|
||||
text += "\n"
|
||||
|
||||
|
||||
text += "📈 Продолжайте развивать свою реферальную сеть!"
|
||||
|
||||
await callback.message.edit_text(
|
||||
|
||||
await edit_or_answer_photo(
|
||||
callback,
|
||||
text,
|
||||
reply_markup=types.InlineKeyboardMarkup(inline_keyboard=[
|
||||
types.InlineKeyboardMarkup(inline_keyboard=[
|
||||
[types.InlineKeyboardButton(text=texts.BACK, callback_data="menu_referrals")]
|
||||
]),
|
||||
parse_mode="HTML"
|
||||
)
|
||||
await callback.answer()
|
||||
|
||||
@@ -257,34 +253,36 @@ async def create_invite_message(
|
||||
db_user: User
|
||||
):
|
||||
texts = get_texts(db_user.language)
|
||||
|
||||
|
||||
bot_username = (await callback.bot.get_me()).username
|
||||
referral_link = f"https://t.me/{bot_username}?start={db_user.referral_code}"
|
||||
|
||||
|
||||
invite_text = f"🎉 Присоединяйся к VPN сервису!\n\n"
|
||||
invite_text += f"💎 При первом пополнении от {texts.format_price(settings.REFERRAL_MINIMUM_TOPUP_KOPEKS)} ты получишь {texts.format_price(settings.REFERRAL_FIRST_TOPUP_BONUS_KOPEKS)} бонусом на баланс!\n\n"
|
||||
invite_text += f"🚀 Быстрое подключение\n"
|
||||
invite_text += f"🌍 Серверы по всему миру\n"
|
||||
invite_text += f"🔒 Надежная защита\n\n"
|
||||
invite_text += f"👇 Переходи по ссылке:\n{referral_link}"
|
||||
|
||||
|
||||
keyboard = types.InlineKeyboardMarkup(inline_keyboard=[
|
||||
[types.InlineKeyboardButton(
|
||||
text="📤 Поделиться",
|
||||
switch_inline_query=invite_text
|
||||
switch_inline_query=invite_text
|
||||
)],
|
||||
[types.InlineKeyboardButton(
|
||||
text=texts.BACK,
|
||||
callback_data="menu_referrals"
|
||||
)]
|
||||
])
|
||||
|
||||
await callback.message.edit_text(
|
||||
f"📝 <b>Приглашение создано!</b>\n\n"
|
||||
f"Нажмите кнопку «📤 Поделиться» чтобы отправить приглашение в любой чат, или скопируйте текст ниже:\n\n"
|
||||
f"<code>{invite_text}</code>",
|
||||
reply_markup=keyboard,
|
||||
parse_mode="HTML"
|
||||
|
||||
await edit_or_answer_photo(
|
||||
callback,
|
||||
(
|
||||
f"📝 <b>Приглашение создано!</b>\n\n"
|
||||
f"Нажмите кнопку «📤 Поделиться» чтобы отправить приглашение в любой чат, или скопируйте текст ниже:\n\n"
|
||||
f"<code>{invite_text}</code>"
|
||||
),
|
||||
keyboard,
|
||||
)
|
||||
await callback.answer()
|
||||
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
from pathlib import Path
|
||||
from aiogram.types import Message, FSInputFile, InputMediaPhoto
|
||||
|
||||
from app.config import settings
|
||||
|
||||
LOGO_PATH = Path(settings.LOGO_FILE)
|
||||
|
||||
|
||||
def is_qr_message(message: Message) -> bool:
|
||||
return bool(message.caption and message.caption.startswith("\U0001F517 Ваша реферальная ссылка"))
|
||||
|
||||
|
||||
_original_answer = Message.answer
|
||||
_original_edit_text = Message.edit_text
|
||||
|
||||
|
||||
async def _answer_with_photo(self: Message, text: str = None, **kwargs):
|
||||
if LOGO_PATH.exists():
|
||||
return await self.answer_photo(FSInputFile(LOGO_PATH), caption=text, **kwargs)
|
||||
return await _original_answer(self, text, **kwargs)
|
||||
|
||||
|
||||
async def _edit_with_photo(self: Message, text: str, **kwargs):
|
||||
if self.photo:
|
||||
media = self.photo[-1].file_id
|
||||
if is_qr_message(self) and LOGO_PATH.exists():
|
||||
media = FSInputFile(LOGO_PATH)
|
||||
media_kwargs = {"media": media, "caption": text}
|
||||
if "parse_mode" in kwargs:
|
||||
media_kwargs["parse_mode"] = kwargs.pop("parse_mode")
|
||||
return await self.edit_media(InputMediaPhoto(**media_kwargs), **kwargs)
|
||||
return await _original_edit_text(self, text, **kwargs)
|
||||
|
||||
|
||||
def patch_message_methods():
|
||||
if not settings.ENABLE_LOGO_MODE:
|
||||
return
|
||||
Message.answer = _answer_with_photo
|
||||
Message.edit_text = _edit_with_photo
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
from aiogram import types
|
||||
from aiogram.exceptions import TelegramBadRequest
|
||||
from aiogram.types import FSInputFile, InputMediaPhoto
|
||||
|
||||
from app.config import settings
|
||||
from .message_patch import LOGO_PATH, is_qr_message
|
||||
|
||||
|
||||
def _resolve_media(message: types.Message):
|
||||
if message.photo and not is_qr_message(message):
|
||||
return message.photo[-1].file_id
|
||||
return FSInputFile(LOGO_PATH)
|
||||
|
||||
|
||||
async def edit_or_answer_photo(
|
||||
callback: types.CallbackQuery,
|
||||
caption: str,
|
||||
keyboard: types.InlineKeyboardMarkup,
|
||||
parse_mode: str | None = "HTML",
|
||||
) -> None:
|
||||
if not settings.ENABLE_LOGO_MODE:
|
||||
try:
|
||||
if callback.message.photo:
|
||||
await callback.message.delete()
|
||||
await callback.message.answer(
|
||||
caption,
|
||||
reply_markup=keyboard,
|
||||
parse_mode=parse_mode,
|
||||
)
|
||||
else:
|
||||
await callback.message.edit_text(
|
||||
caption,
|
||||
reply_markup=keyboard,
|
||||
parse_mode=parse_mode,
|
||||
)
|
||||
except TelegramBadRequest:
|
||||
await callback.message.delete()
|
||||
await callback.message.answer(
|
||||
caption,
|
||||
reply_markup=keyboard,
|
||||
parse_mode=parse_mode,
|
||||
)
|
||||
return
|
||||
|
||||
media = _resolve_media(callback.message)
|
||||
try:
|
||||
await callback.message.edit_media(
|
||||
InputMediaPhoto(media=media, caption=caption, parse_mode=parse_mode),
|
||||
reply_markup=keyboard,
|
||||
)
|
||||
except TelegramBadRequest:
|
||||
await callback.message.delete()
|
||||
await callback.message.answer_photo(
|
||||
FSInputFile(LOGO_PATH),
|
||||
caption=caption,
|
||||
reply_markup=keyboard,
|
||||
parse_mode=parse_mode,
|
||||
)
|
||||
@@ -67,6 +67,8 @@ services:
|
||||
# Timezone
|
||||
- /etc/timezone:/etc/timezone:ro
|
||||
- /etc/localtime:/etc/localtime:ro
|
||||
# Логотип для сообщений
|
||||
- ./vpn_logo.png:/app/vpn_logo.png:ro
|
||||
ports:
|
||||
- "${TRIBUTE_WEBHOOK_PORT:-8081}:8081"
|
||||
- "${YOOKASSA_WEBHOOK_PORT:-8082}:8082"
|
||||
|
||||
Reference in New Issue
Block a user