diff --git a/gotelegram-bot/bot.py b/gotelegram-bot/bot.py
index 89f0683..1d63852 100644
--- a/gotelegram-bot/bot.py
+++ b/gotelegram-bot/bot.py
@@ -288,6 +288,17 @@ async def safe_edit_message(query, text: str, reply_markup=None, parse_mode=None
raise # Re-raise unexpected BadRequest
+async def _delete_message_after(message, delay: int = 30) -> None:
+ """Delete a Telegram message after `delay` seconds. Errors are swallowed
+ (message may already be deleted by the user). Used for ephemeral content
+ like promo blocks that should auto-cleanup."""
+ try:
+ await asyncio.sleep(delay)
+ await message.delete()
+ except Exception as e:
+ logger.debug(f"_delete_message_after: {e}")
+
+
async def check_service_status(service: str) -> bool:
"""Check if systemd service is running."""
code, _, _ = await sh("systemctl", "is-active", service)
@@ -465,12 +476,13 @@ async def cmd_start(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
welcome, reply_markup=get_main_menu(user_id), parse_mode="HTML"
)
- # Промо раз в сутки
+ # Промо раз в сутки — сообщение само удаляется через 30 секунд
if should_show_promo_bot():
mark_promo_shown_bot()
- await update.message.reply_text(
- get_promo_text(), parse_mode="HTML"
+ promo_msg = await update.message.reply_text(
+ get_promo_text(), parse_mode="HTML", disable_web_page_preview=True
)
+ asyncio.create_task(_delete_message_after(promo_msg, 30))
async def cmd_help(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
@@ -796,7 +808,17 @@ async def cb_install_mode_lite(update: Update, context: ContextTypes.DEFAULT_TYP
async def cb_lite_domain(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
- """Lite domain selection callback."""
+ """Lite domain selection callback — safe stub.
+
+ Historic bug (v2.4.1): this callback used to overwrite
+ /opt/gotelegram/config.json with a minimal fake config (no secret, no
+ mask_host, no engine), silently corrupting the live proxy and showing a
+ fake "✅ Lite mode installed!" — while never actually invoking install.sh.
+
+ Installing/changing modes non-interactively from the bot is not yet wired
+ up. Until it is, refuse cleanly and route the user to the CLI instead of
+ damaging the working configuration.
+ """
query = update.callback_query
data = query.data
try:
@@ -807,36 +829,19 @@ async def cb_lite_domain(update: Update, context: ContextTypes.DEFAULT_TYPE) ->
return
await query.answer()
- await safe_edit_message(query,f"⏳ Installing with domain: {domain}...")
- # Simulate installation (in real scenario, call install script)
- config = {
- "mode": "lite",
- "domain": domain,
- "port": 443,
- "installed_at": datetime.now().isoformat(),
- }
-
- if save_json(GOTELEGRAM_CONFIG, config):
- text = (
- f"✅ Lite mode installed!\n\n"
- f"Domain: {domain}\n"
- f"Mode: Lite\n\n"
- f"Service starting... Check status in 10 seconds."
- )
- keyboard = InlineKeyboardMarkup(
- [[InlineKeyboardButton(_t(_uid(update), "btn_back"), callback_data="menu_main")]]
- )
- await safe_edit_message(query,
- text, reply_markup=keyboard, parse_mode="HTML"
- )
- else:
- await safe_edit_message(query,
- "❌ Failed to save configuration",
- reply_markup=InlineKeyboardMarkup(
- [[InlineKeyboardButton("« Back", callback_data="menu_install")]]
- ),
- )
+ text = (
+ "⚠️ Установка из бота пока не поддерживается\n\n"
+ f"Выбранный домен: {html.escape(domain)}\n\n"
+ "Чтобы установить или переключить режим Lite, запустите на сервере:\n"
+ "gotelegram\n\n"
+ "Затем: 1) Прокси → 1) Установить/Обновить → Lite.\n\n"
+ "Существующая конфигурация не была изменена."
+ )
+ keyboard = InlineKeyboardMarkup(
+ [[InlineKeyboardButton(_t(_uid(update), "btn_back"), callback_data="menu_main")]]
+ )
+ await safe_edit_message(query, text, reply_markup=keyboard, parse_mode="HTML")
async def cb_install_mode_pro(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
@@ -1106,41 +1111,39 @@ async def cb_pro_template(update: Update, context: ContextTypes.DEFAULT_TYPE) ->
async def cb_pro_confirm(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
- """Confirm and install pro template."""
+ """Confirm Pro template selection — safe stub.
+
+ Historic bug (v2.4.1): this callback used to overwrite
+ /opt/gotelegram/config.json with a fake {"mode":"pro","template":...,
+ "port":443} blob — no secret, no domain, no mask_host — silently
+ corrupting the live proxy so link generation and status broke. At the
+ same time the user saw "✅ Pro mode installed!" although install.sh was
+ never invoked, no template was actually downloaded, nginx was not
+ reconfigured and certbot was not touched.
+
+ Non-interactive Pro install/change from the bot is not yet implemented.
+ Until it is, refuse cleanly and route the user to the CLI instead of
+ damaging the working configuration.
+ """
query = update.callback_query
data = query.data
tpl_id = data.removeprefix("pro_confirm_")
await query.answer()
- await safe_edit_message(query,"⏳ Installing template...")
- config = {
- "mode": "pro",
- "template": tpl_id,
- "port": 443,
- "installed_at": datetime.now().isoformat(),
- }
-
- if save_json(GOTELEGRAM_CONFIG, config):
- text = (
- f"✅ Pro mode installed!\n\n"
- f"Template: {html.escape(tpl_id)}\n"
- f"Mode: Pro\n\n"
- f"Service starting... Check status in 10 seconds."
- )
- keyboard = InlineKeyboardMarkup(
- [[InlineKeyboardButton(_t(_uid(update), "btn_back"), callback_data="menu_main")]]
- )
- await safe_edit_message(query,
- text, reply_markup=keyboard, parse_mode="HTML"
- )
- else:
- await safe_edit_message(query,
- "❌ Failed to save configuration",
- reply_markup=InlineKeyboardMarkup(
- [[InlineKeyboardButton("« Back", callback_data="menu_install")]]
- ),
- )
+ text = (
+ "⚠️ Установка шаблона из бота пока не поддерживается\n\n"
+ f"Выбранный шаблон: {html.escape(tpl_id)}\n\n"
+ "Чтобы установить или сменить шаблон, запустите на сервере:\n"
+ "gotelegram\n\n"
+ "Затем: 1) Прокси → 1) Установить/Обновить → Pro "
+ "(или 7) Сменить режим/шаблон для смены текущего).\n\n"
+ "Существующая конфигурация не была изменена."
+ )
+ keyboard = InlineKeyboardMarkup(
+ [[InlineKeyboardButton(_t(_uid(update), "btn_back"), callback_data="menu_main")]]
+ )
+ await safe_edit_message(query, text, reply_markup=keyboard, parse_mode="HTML")
# ============================================================================
@@ -1799,14 +1802,16 @@ def mark_promo_shown_bot() -> None:
async def cb_menu_promo(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
- """Promo information — always shown from menu."""
+ """Promo information — shown as a separate ephemeral message that
+ auto-deletes after 30s so it does not clutter the chat. The main menu
+ message stays intact (we don't edit it in place)."""
query = update.callback_query
await query.answer()
- keyboard = InlineKeyboardMarkup(
- [[InlineKeyboardButton(_t(_uid(update), "btn_back"), callback_data="menu_main")]]
+ promo_msg = await query.message.reply_text(
+ get_promo_text(), parse_mode="HTML", disable_web_page_preview=True
)
- await safe_edit_message(query, get_promo_text(), reply_markup=keyboard, parse_mode="HTML")
+ asyncio.create_task(_delete_message_after(promo_msg, 30))
async def cb_menu_credits(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None: