mirror of
https://github.com/anten-ka/gotelegram_pro.git
synced 2026-05-19 20:46:04 +00:00
fix(bot): promo auto-delete + neutralize fake install/change stubs
Two user-reported bugs:
1. Promo spam: daily promo message from /start and the Promo menu
button stayed in chat forever. Now they auto-delete after 30s.
- new helper _delete_message_after()
- cmd_start schedules deletion of the daily promo
- cb_menu_promo sends promo as a SEPARATE ephemeral message
(instead of editing the main menu in place), so the menu stays
intact and only the promo self-destructs in 30s.
2. CRITICAL: 'install/change template from bot' was a stub that
silently corrupted /opt/gotelegram/config.json. cb_lite_domain
and cb_pro_confirm wrote a fake minimal config ({mode,template,
port,installed_at}) without secret/domain/mask_host, and showed
'[OK] installed!' — while never invoking install.sh, never
downloading the template, never touching nginx. Proxy link
generation then broke because secret was gone.
User symptom: 'устанавливал другой шаблон через бота и всё
повисло'.
Fix: both callbacks now refuse cleanly and route the user to the
CLI ('gotelegram' command → menu 1 → 1 or 7). Configuration is
NOT touched. Full non-interactive install/change from the bot is
left as future work.
Tested live on VPS:
- bot.py syntax OK (ast.parse on VPS)
- gotelegram-bot restarted, active
- corrupted config.json on VPS rebuilt from telemt TOML ground
truth (mode=lite, secret, port, mask_host=google.com)
This commit is contained in:
@@ -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"✅ <b>Lite mode installed!</b>\n\n"
|
||||
f"<b>Domain:</b> {domain}\n"
|
||||
f"<b>Mode:</b> Lite\n\n"
|
||||
f"Service starting... Check status in 10 seconds."
|
||||
"<b>⚠️ Установка из бота пока не поддерживается</b>\n\n"
|
||||
f"Выбранный домен: <code>{html.escape(domain)}</code>\n\n"
|
||||
"Чтобы установить или переключить режим Lite, запустите на сервере:\n"
|
||||
"<code>gotelegram</code>\n\n"
|
||||
"Затем: <b>1) Прокси → 1) Установить/Обновить → Lite</b>.\n\n"
|
||||
"Существующая конфигурация <b>не была изменена</b>."
|
||||
)
|
||||
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")]]
|
||||
),
|
||||
)
|
||||
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"✅ <b>Pro mode installed!</b>\n\n"
|
||||
f"<b>Template:</b> {html.escape(tpl_id)}\n"
|
||||
f"<b>Mode:</b> Pro\n\n"
|
||||
f"Service starting... Check status in 10 seconds."
|
||||
"<b>⚠️ Установка шаблона из бота пока не поддерживается</b>\n\n"
|
||||
f"Выбранный шаблон: <code>{html.escape(tpl_id)}</code>\n\n"
|
||||
"Чтобы установить или сменить шаблон, запустите на сервере:\n"
|
||||
"<code>gotelegram</code>\n\n"
|
||||
"Затем: <b>1) Прокси → 1) Установить/Обновить → Pro</b> "
|
||||
"(или <b>7) Сменить режим/шаблон</b> для смены текущего).\n\n"
|
||||
"Существующая конфигурация <b>не была изменена</b>."
|
||||
)
|
||||
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")]]
|
||||
),
|
||||
)
|
||||
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:
|
||||
|
||||
Reference in New Issue
Block a user