mirror of
https://github.com/anten-ka/gotelegram_pro.git
synced 2026-05-19 14:36:05 +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
|
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:
|
async def check_service_status(service: str) -> bool:
|
||||||
"""Check if systemd service is running."""
|
"""Check if systemd service is running."""
|
||||||
code, _, _ = await sh("systemctl", "is-active", service)
|
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"
|
welcome, reply_markup=get_main_menu(user_id), parse_mode="HTML"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Промо раз в сутки
|
# Промо раз в сутки — сообщение само удаляется через 30 секунд
|
||||||
if should_show_promo_bot():
|
if should_show_promo_bot():
|
||||||
mark_promo_shown_bot()
|
mark_promo_shown_bot()
|
||||||
await update.message.reply_text(
|
promo_msg = await update.message.reply_text(
|
||||||
get_promo_text(), parse_mode="HTML"
|
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:
|
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:
|
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
|
query = update.callback_query
|
||||||
data = query.data
|
data = query.data
|
||||||
try:
|
try:
|
||||||
@@ -807,36 +829,19 @@ async def cb_lite_domain(update: Update, context: ContextTypes.DEFAULT_TYPE) ->
|
|||||||
return
|
return
|
||||||
|
|
||||||
await query.answer()
|
await query.answer()
|
||||||
await safe_edit_message(query,f"⏳ Installing with domain: {domain}...")
|
|
||||||
|
|
||||||
# Simulate installation (in real scenario, call install script)
|
text = (
|
||||||
config = {
|
"<b>⚠️ Установка из бота пока не поддерживается</b>\n\n"
|
||||||
"mode": "lite",
|
f"Выбранный домен: <code>{html.escape(domain)}</code>\n\n"
|
||||||
"domain": domain,
|
"Чтобы установить или переключить режим Lite, запустите на сервере:\n"
|
||||||
"port": 443,
|
"<code>gotelegram</code>\n\n"
|
||||||
"installed_at": datetime.now().isoformat(),
|
"Затем: <b>1) Прокси → 1) Установить/Обновить → Lite</b>.\n\n"
|
||||||
}
|
"Существующая конфигурация <b>не была изменена</b>."
|
||||||
|
)
|
||||||
if save_json(GOTELEGRAM_CONFIG, config):
|
keyboard = InlineKeyboardMarkup(
|
||||||
text = (
|
[[InlineKeyboardButton(_t(_uid(update), "btn_back"), callback_data="menu_main")]]
|
||||||
f"✅ <b>Lite mode installed!</b>\n\n"
|
)
|
||||||
f"<b>Domain:</b> {domain}\n"
|
await safe_edit_message(query, text, reply_markup=keyboard, parse_mode="HTML")
|
||||||
f"<b>Mode:</b> 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")]]
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
async def cb_install_mode_pro(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
|
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:
|
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
|
query = update.callback_query
|
||||||
data = query.data
|
data = query.data
|
||||||
tpl_id = data.removeprefix("pro_confirm_")
|
tpl_id = data.removeprefix("pro_confirm_")
|
||||||
|
|
||||||
await query.answer()
|
await query.answer()
|
||||||
await safe_edit_message(query,"⏳ Installing template...")
|
|
||||||
|
|
||||||
config = {
|
text = (
|
||||||
"mode": "pro",
|
"<b>⚠️ Установка шаблона из бота пока не поддерживается</b>\n\n"
|
||||||
"template": tpl_id,
|
f"Выбранный шаблон: <code>{html.escape(tpl_id)}</code>\n\n"
|
||||||
"port": 443,
|
"Чтобы установить или сменить шаблон, запустите на сервере:\n"
|
||||||
"installed_at": datetime.now().isoformat(),
|
"<code>gotelegram</code>\n\n"
|
||||||
}
|
"Затем: <b>1) Прокси → 1) Установить/Обновить → Pro</b> "
|
||||||
|
"(или <b>7) Сменить режим/шаблон</b> для смены текущего).\n\n"
|
||||||
if save_json(GOTELEGRAM_CONFIG, config):
|
"Существующая конфигурация <b>не была изменена</b>."
|
||||||
text = (
|
)
|
||||||
f"✅ <b>Pro mode installed!</b>\n\n"
|
keyboard = InlineKeyboardMarkup(
|
||||||
f"<b>Template:</b> {html.escape(tpl_id)}\n"
|
[[InlineKeyboardButton(_t(_uid(update), "btn_back"), callback_data="menu_main")]]
|
||||||
f"<b>Mode:</b> Pro\n\n"
|
)
|
||||||
f"Service starting... Check status in 10 seconds."
|
await safe_edit_message(query, text, reply_markup=keyboard, parse_mode="HTML")
|
||||||
)
|
|
||||||
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")]]
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
@@ -1799,14 +1802,16 @@ def mark_promo_shown_bot() -> None:
|
|||||||
|
|
||||||
|
|
||||||
async def cb_menu_promo(update: Update, context: ContextTypes.DEFAULT_TYPE) -> 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
|
query = update.callback_query
|
||||||
await query.answer()
|
await query.answer()
|
||||||
|
|
||||||
keyboard = InlineKeyboardMarkup(
|
promo_msg = await query.message.reply_text(
|
||||||
[[InlineKeyboardButton(_t(_uid(update), "btn_back"), callback_data="menu_main")]]
|
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:
|
async def cb_menu_credits(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
|
||||||
|
|||||||
Reference in New Issue
Block a user