chore: remove promo surfaces

This commit is contained in:
Андрей Бобырев
2026-06-06 13:50:31 +03:00
parent 578378f6d8
commit 931fcba16b
10 changed files with 16 additions and 299 deletions

View File

@@ -215,11 +215,6 @@ const i18n = {
ariaTrafficHistory: "Traffic history", ariaTrafficHistory: "Traffic history",
ariaTrafficRange: "Traffic range", ariaTrafficRange: "Traffic range",
ariaTrafficView: "Traffic view", ariaTrafficView: "Traffic view",
promoEyebrow: "Promo",
promoTitle: "Support PCAtelegram_web",
promoHosting1: "Hosting #1",
promoHosting2: "Hosting #2",
promoTips: "Tips",
qrEyebrow: "QR import", qrEyebrow: "QR import",
qrTitle: "Scan Telegram proxy", qrTitle: "Scan Telegram proxy",
pageDashboardTitle: "Dashboard", pageDashboardTitle: "Dashboard",
@@ -448,11 +443,6 @@ const i18n = {
ariaTrafficHistory: "История трафика", ariaTrafficHistory: "История трафика",
ariaTrafficRange: "Период трафика", ariaTrafficRange: "Период трафика",
ariaTrafficView: "Вид трафика", ariaTrafficView: "Вид трафика",
promoEyebrow: "Промо",
promoTitle: "Поддержать PCAtelegram_web",
promoHosting1: "Хостинг #1",
promoHosting2: "Хостинг #2",
promoTips: "Чаевые",
qrEyebrow: "QR-импорт", qrEyebrow: "QR-импорт",
qrTitle: "Сканирование прокси Telegram", qrTitle: "Сканирование прокси Telegram",
pageDashboardTitle: "Обзор", pageDashboardTitle: "Обзор",
@@ -1675,15 +1665,6 @@ async function copyText(value) {
} }
} }
function maybeShowPromo() {
const key = "pcatelegram_web-promo-last";
const now = Math.floor(Date.now() / 1000);
const last = Number(localStorage.getItem(key) || 0);
if (now - last < 86400) return;
localStorage.setItem(key, String(now));
$("#promoModal").hidden = false;
}
document.addEventListener("click", async (eventObj) => { document.addEventListener("click", async (eventObj) => {
const nav = eventObj.target.closest("[data-nav]"); const nav = eventObj.target.closest("[data-nav]");
if (nav) { if (nav) {
@@ -1768,9 +1749,6 @@ document.addEventListener("submit", (eventObj) => {
$("#refreshBtn").addEventListener("click", refreshAll); $("#refreshBtn").addEventListener("click", refreshAll);
$("#autoRefreshToggle").addEventListener("click", () => setAutoRefresh(!state.autoRefreshEnabled)); $("#autoRefreshToggle").addEventListener("click", () => setAutoRefresh(!state.autoRefreshEnabled));
$("#languageSelect").addEventListener("change", (eventObj) => setLanguage(eventObj.target.value)); $("#languageSelect").addEventListener("change", (eventObj) => setLanguage(eventObj.target.value));
$("#promoClose").addEventListener("click", () => {
$("#promoModal").hidden = true;
});
$("#qrClose").addEventListener("click", () => { $("#qrClose").addEventListener("click", () => {
$("#qrModal").hidden = true; $("#qrModal").hidden = true;
}); });
@@ -1790,4 +1768,3 @@ renderEvents();
syncAutoRefreshTimer(); syncAutoRefreshTimer();
refreshAll(); refreshAll();
loadLogs(); loadLogs();
maybeShowPromo();

View File

@@ -376,8 +376,8 @@
</div> </div>
<div id="toast" class="toast"></div> <div id="toast" class="toast"></div>
<div id="qrModal" class="promo-modal" hidden> <div id="qrModal" class="modal-overlay" hidden>
<div class="promo-card qr-card" role="dialog" aria-modal="true" aria-labelledby="qrTitle"> <div class="modal-card qr-card" role="dialog" aria-modal="true" aria-labelledby="qrTitle">
<button id="qrClose" class="icon-btn ghost" type="button" aria-label="Close" data-i18n-aria-label="ariaClose">×</button> <button id="qrClose" class="icon-btn ghost" type="button" aria-label="Close" data-i18n-aria-label="ariaClose">×</button>
<p class="eyebrow" data-i18n="qrEyebrow">QR import</p> <p class="eyebrow" data-i18n="qrEyebrow">QR import</p>
<h2 id="qrTitle" data-i18n="qrTitle">Scan Telegram proxy</h2> <h2 id="qrTitle" data-i18n="qrTitle">Scan Telegram proxy</h2>
@@ -388,27 +388,6 @@
<button id="qrCopyBtn" type="button" class="soft" data-i18n="copyLink">Copy link</button> <button id="qrCopyBtn" type="button" class="soft" data-i18n="copyLink">Copy link</button>
</div> </div>
</div> </div>
<div id="promoModal" class="promo-modal" hidden>
<div class="promo-card" role="dialog" aria-modal="true" aria-labelledby="promoTitle">
<button id="promoClose" class="icon-btn ghost" type="button" aria-label="Close" data-i18n-aria-label="ariaClose">×</button>
<p class="eyebrow" data-i18n="promoEyebrow">Promo</p>
<h2 id="promoTitle" data-i18n="promoTitle">Support PCAtelegram_web</h2>
<div class="promo-grid">
<a href="https://vk.cc/ct29NQ" target="_blank" rel="noreferrer">
<strong data-i18n="promoHosting1">Hosting #1</strong>
<span>OFF60 · BONUS20 · BONUS6</span>
</a>
<a href="https://vk.cc/cUxAhj" target="_blank" rel="noreferrer">
<strong data-i18n="promoHosting2">Hosting #2</strong>
<span>OFF60</span>
</a>
<a href="https://pay.cloudtips.ru/p/7410814f" target="_blank" rel="noreferrer">
<strong data-i18n="promoTips">Tips</strong>
<span>CloudTips</span>
</a>
</div>
</div>
</div>
<script src="/app.js?v=2.5.0-admin19" type="module"></script> <script src="/app.js?v=2.5.0-admin19" type="module"></script>
</body> </body>
</html> </html>

View File

@@ -1362,7 +1362,7 @@ td small {
transform: translateY(0); transform: translateY(0);
} }
.promo-modal { .modal-overlay {
position: fixed; position: fixed;
inset: 0; inset: 0;
z-index: 30; z-index: 30;
@@ -1372,11 +1372,11 @@ td small {
background: rgba(5, 8, 16, .48); background: rgba(5, 8, 16, .48);
} }
.promo-modal[hidden] { .modal-overlay[hidden] {
display: none; display: none;
} }
.promo-card { .modal-card {
position: relative; position: relative;
width: min(620px, 100%); width: min(620px, 100%);
border: 1px solid var(--line); border: 1px solid var(--line);
@@ -1386,40 +1386,12 @@ td small {
padding: 22px; padding: 22px;
} }
.promo-card .icon-btn { .modal-card .icon-btn {
position: absolute; position: absolute;
top: 14px; top: 14px;
right: 14px; right: 14px;
} }
.promo-grid {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 10px;
margin-top: 18px;
}
.promo-grid a {
display: grid;
gap: 6px;
min-height: 98px;
border: 1px solid var(--line);
border-radius: 8px;
background: var(--panel-soft);
color: var(--text);
padding: 14px;
text-decoration: none;
}
.promo-grid a:hover {
border-color: var(--green);
}
.promo-grid span {
color: var(--muted);
font-size: 12px;
}
@media (max-width: 1180px) { @media (max-width: 1180px) {
.keys-table { .keys-table {
min-width: 0; min-width: 0;
@@ -1536,8 +1508,7 @@ td small {
} }
@media (max-width: 720px) { @media (max-width: 720px) {
.visual-overview, .visual-overview {
.promo-grid {
grid-template-columns: 1fr; grid-template-columns: 1fr;
} }

View File

@@ -215,7 +215,6 @@ submenu_about() {
echo -e " ${BOLD}${WHITE}$(t submenu_about_title)${NC}" echo -e " ${BOLD}${WHITE}$(t submenu_about_title)${NC}"
echo -e " ${DIM}$(printf '─%.0s' {1..54})${NC}" echo -e " ${DIM}$(printf '─%.0s' {1..54})${NC}"
echo -e " ${CYAN}1${NC}) $(t about_version_info)" echo -e " ${CYAN}1${NC}) $(t about_version_info)"
echo -e " ${CYAN}2${NC}) $(t about_promo)"
echo -e " ${CYAN}0${NC}) $(t back)" echo -e " ${CYAN}0${NC}) $(t back)"
echo -e " ${DIM}$(printf '─%.0s' {1..54})${NC}" echo -e " ${DIM}$(printf '─%.0s' {1..54})${NC}"
echo -ne " ${WHITE}$(t choose):${NC} " echo -ne " ${WHITE}$(t choose):${NC} "
@@ -223,7 +222,6 @@ submenu_about() {
case "$ch" in case "$ch" in
1) menu_version ;; 1) menu_version ;;
2) menu_promo ;;
0) break ;; 0) break ;;
*) log_error "$(t invalid_choice)" ;; *) log_error "$(t invalid_choice)" ;;
esac esac
@@ -1407,89 +1405,6 @@ bot_remove() {
log_success "$(t bot_removed)" log_success "$(t bot_removed)"
} }
# ── Promo ────────────────────────────────────────────────────────────────────
_promo_block() {
# Print a promo section without width-fragile box borders (i18n safe)
local line2; line2=$(printf '─%.0s' {1..54})
local youtube_link="${PCATELEGRAM_WEB_YOUTUBE_LINK:-}"
echo ""
echo -e " ${DIM}${line2}${NC}"
echo -e " ${BOLD}${YELLOW}$(t promo_host1_title)${NC}"
echo -e " $(t promo_link_label) ${CYAN}https://vk.cc/ct29NQ${NC}"
echo -e " ${WHITE}OFF60${NC}$(tf promo_off60)"
echo -e " ${WHITE}BONUS20${NC}$(tf promo_ant20)"
echo -e " ${WHITE}BONUS6${NC}$(tf promo_ant6)"
echo -e " ${DIM}${line2}${NC}"
echo -e " ${BOLD}${YELLOW}$(t promo_host2_title)${NC}"
echo -e " $(t promo_link_label) ${CYAN}https://vk.cc/cUxAhj${NC}"
echo -e " ${WHITE}OFF60${NC}$(tf promo_off60)"
echo -e " ${DIM}${line2}${NC}"
echo -e " ${BOLD}${YELLOW}$(t promo_tips_title)${NC}"
echo -e " ${CYAN}https://pay.cloudtips.ru/p/7410814f${NC}"
if [ -n "$youtube_link" ]; then
echo -e " ${DIM}${line2}${NC}"
echo -e " ${BOLD}${YELLOW}$(t promo_youtube_title)${NC}"
echo -e " $(t promo_link_label) ${CYAN}${youtube_link}${NC}"
fi
echo -e " ${DIM}${line2}${NC}"
echo ""
}
menu_promo() {
_promo_block
}
# ── Проверка: показывать ли промо (раз в сутки) ────────────────────────────
should_show_promo() {
local stamp_file="$PCATELEGRAM_WEB_DIR/.promo_last_shown"
if [ ! -f "$stamp_file" ]; then
return 0 # никогда не показывали
fi
local last_shown now diff
last_shown=$(cat "$stamp_file" 2>/dev/null || echo "0")
last_shown="${last_shown//[^0-9]/}"
last_shown="${last_shown:-0}"
now=$(date +%s)
diff=$(( now - last_shown ))
# 86400 = 24 часа
[ "$diff" -ge 86400 ]
}
mark_promo_shown() {
mkdir -p "$PCATELEGRAM_WEB_DIR"
date +%s > "$PCATELEGRAM_WEB_DIR/.promo_last_shown"
}
_promo_qr() {
local label="$1" url="$2"
[ -n "$url" ] || return 0
echo -e " ${DIM}${label}${NC}"
qrencode -t UTF8 -m 1 "$url" 2>/dev/null | while IFS= read -r qr_line; do
echo " $qr_line"
done
}
# ── Promo with QR + delay (on install + once per day) ───────────────────
show_promo_with_qr() {
_promo_block
if command -v qrencode &>/dev/null; then
_promo_qr "$(t promo_qr_host1)" "https://vk.cc/ct29NQ"
_promo_qr "$(t promo_qr_host2)" "https://vk.cc/cUxAhj"
_promo_qr "$(t promo_qr_tips)" "https://pay.cloudtips.ru/p/7410814f"
_promo_qr "$(t promo_qr_youtube)" "${PCATELEGRAM_WEB_YOUTUBE_LINK:-}"
fi
mark_promo_shown
# 5-second countdown
for i in 5 4 3 2 1; do
echo -ne "\r ${DIM}$(tf promo_menu_in "$i")${NC} "
sleep 1
done
echo -ne "\r \r"
}
# ── First-run: pick language ───────────────────────────────────────────────── # ── First-run: pick language ─────────────────────────────────────────────────
first_run_language_picker() { first_run_language_picker() {
# Show picker only if language not yet saved # Show picker only if language not yet saved
@@ -1829,11 +1744,6 @@ main() {
check_os check_os
check_disk_space 500 check_disk_space 500
# Promo once per day
if should_show_promo; then
show_promo_with_qr
fi
while true; do while true; do
clear clear
show_main_menu show_main_menu

View File

@@ -73,7 +73,6 @@ I18N[manage_language]="Language / Язык"
# ── Submenu: About ────────────────────────────────────────────────────── # ── Submenu: About ──────────────────────────────────────────────────────
I18N[submenu_about_title]=" ABOUT" I18N[submenu_about_title]=" ABOUT"
I18N[about_version_info]="Version info" I18N[about_version_info]="Version info"
I18N[about_promo]="Promo / Donate"
I18N[version_title]="🔍 Information" I18N[version_title]="🔍 Information"
I18N[version_label]="PCAtelegram_web:" I18N[version_label]="PCAtelegram_web:"
I18N[version_engine]="Engine:" I18N[version_engine]="Engine:"
@@ -214,21 +213,6 @@ I18N[bot_status_colon]="Status:"
I18N[bot_access_colon]="Access:" I18N[bot_access_colon]="Access:"
I18N[bot_access_ids_fmt]="ID: %s" I18N[bot_access_ids_fmt]="ID: %s"
# ── Promo / Donate ──────────────────────────────────────────────────────
I18N[promo_host1_title]="💰 HOSTING #1 — UP TO 60% OFF"
I18N[promo_host2_title]="💰 HOSTING #2 — UP TO 60% OFF"
I18N[promo_tips_title]="☕ Donate / Tips"
I18N[promo_youtube_title]="▶ YouTube Channel"
I18N[promo_link_label]="Link:"
I18N[promo_off60]="60%% discount on the first month"
I18N[promo_ant20]="20%% + 3%% when paid for 3 months"
I18N[promo_ant6]="15%% + 5%% when paid for 6 months"
I18N[promo_qr_host1]="── QR: Hosting #1 ──"
I18N[promo_qr_host2]="── QR: Hosting #2 ──"
I18N[promo_qr_tips]="── QR: Donate / Tips ──"
I18N[promo_qr_youtube]="── QR: YouTube Channel ──"
I18N[promo_menu_in]="Menu in %d sec..."
# ── Stats ─────────────────────────────────────────────────────────────── # ── Stats ───────────────────────────────────────────────────────────────
I18N[stats_title]="📊 Traffic statistics" I18N[stats_title]="📊 Traffic statistics"
I18N[stats_module_missing]="Statistics module not loaded." I18N[stats_module_missing]="Statistics module not loaded."

View File

@@ -73,7 +73,6 @@ I18N[manage_language]="Язык / Language"
# ── Submenu: About ────────────────────────────────────────────────────── # ── Submenu: About ──────────────────────────────────────────────────────
I18N[submenu_about_title]=" О ПРОГРАММЕ" I18N[submenu_about_title]=" О ПРОГРАММЕ"
I18N[about_version_info]="Информация о версии" I18N[about_version_info]="Информация о версии"
I18N[about_promo]="Промо / Донат"
I18N[version_title]="🔍 Информация" I18N[version_title]="🔍 Информация"
I18N[version_label]="PCAtelegram_web:" I18N[version_label]="PCAtelegram_web:"
I18N[version_engine]="Ядро:" I18N[version_engine]="Ядро:"
@@ -214,21 +213,6 @@ I18N[bot_status_colon]="Статус:"
I18N[bot_access_colon]="Доступ:" I18N[bot_access_colon]="Доступ:"
I18N[bot_access_ids_fmt]="ID: %s" I18N[bot_access_ids_fmt]="ID: %s"
# ── Promo / Donate ──────────────────────────────────────────────────────
I18N[promo_host1_title]="💰 ХОСТИНГ #1 — СКИДКА ДО 60%"
I18N[promo_host2_title]="💰 ХОСТИНГ #2 — СКИДКА ДО 60%"
I18N[promo_tips_title]="☕ Донат / Чаевые"
I18N[promo_youtube_title]="▶ YouTube-канал"
I18N[promo_link_label]="Ссылка:"
I18N[promo_off60]="60%% скидки на первый месяц"
I18N[promo_ant20]="20%% + 3%% при оплате за 3 месяца"
I18N[promo_ant6]="15%% + 5%% при оплате за 6 месяцев"
I18N[promo_qr_host1]="── QR: Хостинг #1 ──"
I18N[promo_qr_host2]="── QR: Хостинг #2 ──"
I18N[promo_qr_tips]="── QR: Чаевые / Донат ──"
I18N[promo_qr_youtube]="── QR: YouTube-канал ──"
I18N[promo_menu_in]="Меню через %d сек..."
# ── Stats ─────────────────────────────────────────────────────────────── # ── Stats ───────────────────────────────────────────────────────────────
I18N[stats_title]="📊 Статистика трафика" I18N[stats_title]="📊 Статистика трафика"
I18N[stats_module_missing]="Модуль статистики не загружен." I18N[stats_module_missing]="Модуль статистики не загружен."

View File

@@ -16,7 +16,6 @@ Production-quality Telegram bot for managing MTProxy (telemt engine) on Linux se
- telemt updates - telemt updates
- Website/SSL management - Website/SSL management
- Remove installation - Remove installation
- Promotional links
- **Template Browsing** - Browse categories → templates → preview → install - **Template Browsing** - Browse categories → templates → preview → install
- **Per-user MTProxy Keys** - Manage telemt `[access.users]` from inline bot menus - **Per-user MTProxy Keys** - Manage telemt `[access.users]` from inline bot menus

View File

@@ -116,12 +116,6 @@ BACKUP_SCHEDULE_FILE = "/opt/pcatelegram_web/backup_schedule.json"
TEMPLATES_CATALOG = "/opt/pcatelegram_web/templates_catalog.json" TEMPLATES_CATALOG = "/opt/pcatelegram_web/templates_catalog.json"
INSTALL_SH = "/opt/pcatelegram_web/install.sh" INSTALL_SH = "/opt/pcatelegram_web/install.sh"
PROMO_LINK_1 = "https://vk.cc/ct29NQ"
PROMO_LINK_2 = "https://vk.cc/cUxAhj"
TIP_LINK = "https://pay.cloudtips.ru/p/7410814f"
YOUTUBE_LINK = os.getenv("PCATELEGRAM_WEB_YOUTUBE_LINK", "").strip()
PROMO_STAMP_FILE = "/opt/pcatelegram_web/.promo_bot_last_shown"
BOT_TOKEN = os.getenv("BOT_TOKEN") BOT_TOKEN = os.getenv("BOT_TOKEN")
ENV_FILE = "/opt/pcatelegram_web-bot/.env" ENV_FILE = "/opt/pcatelegram_web-bot/.env"
ADMIN_WEB_SERVICE = "pcatelegram_web-admin" ADMIN_WEB_SERVICE = "pcatelegram_web-admin"
@@ -470,7 +464,7 @@ async def safe_edit_message(
async def _delete_message_after(message, delay: int = 30) -> None: async def _delete_message_after(message, delay: int = 30) -> None:
"""Delete a Telegram message after `delay` seconds. Errors are swallowed """Delete a Telegram message after `delay` seconds. Errors are swallowed
(message may already be deleted by the user). Used for ephemeral content (message may already be deleted by the user). Used for ephemeral content
like promo blocks that should auto-cleanup.""" like temporary notices that should auto-cleanup."""
try: try:
await asyncio.sleep(delay) await asyncio.sleep(delay)
await message.delete() await message.delete()
@@ -593,22 +587,21 @@ def get_main_menu(user_id: Optional[int] = None) -> InlineKeyboardMarkup:
], ],
[ [
InlineKeyboardButton(_t(user_id, "menu_website"), callback_data="menu_website"), InlineKeyboardButton(_t(user_id, "menu_website"), callback_data="menu_website"),
InlineKeyboardButton(_t(user_id, "menu_promo"), callback_data="menu_promo"),
],
[
InlineKeyboardButton(_t(user_id, "menu_stats"), callback_data="menu_stats"), InlineKeyboardButton(_t(user_id, "menu_stats"), callback_data="menu_stats"),
],
[
InlineKeyboardButton(_t(user_id, "menu_users"), callback_data="menu_users"), InlineKeyboardButton(_t(user_id, "menu_users"), callback_data="menu_users"),
],
[
InlineKeyboardButton(_t(user_id, "menu_admin_web"), callback_data="menu_admin_web"), InlineKeyboardButton(_t(user_id, "menu_admin_web"), callback_data="menu_admin_web"),
],
[
InlineKeyboardButton(_t(user_id, "menu_admins"), callback_data="menu_admins"), InlineKeyboardButton(_t(user_id, "menu_admins"), callback_data="menu_admins"),
],
[
InlineKeyboardButton(_t(user_id, "menu_remove"), callback_data="menu_remove"), InlineKeyboardButton(_t(user_id, "menu_remove"), callback_data="menu_remove"),
InlineKeyboardButton(_t(user_id, "menu_credits"), callback_data="menu_credits"),
], ],
[ [
InlineKeyboardButton(_t(user_id, "menu_credits"), callback_data="menu_credits"),
InlineKeyboardButton(_t(user_id, "menu_language"), callback_data="menu_lang"), InlineKeyboardButton(_t(user_id, "menu_language"), callback_data="menu_lang"),
],
[
InlineKeyboardButton(_t(user_id, "menu_close"), callback_data="close_menu"), InlineKeyboardButton(_t(user_id, "menu_close"), callback_data="close_menu"),
], ],
] ]
@@ -621,7 +614,7 @@ def get_main_menu(user_id: Optional[int] = None) -> InlineKeyboardMarkup:
async def cmd_start(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None: async def cmd_start(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
"""Start command - show main menu, promo once per day. """Start command - show main menu.
Если ALLOWED_IDS пуст — режим авто-регистрации первого админа. Если ALLOWED_IDS пуст — режим авто-регистрации первого админа.
""" """
@@ -661,14 +654,6 @@ 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():
mark_promo_shown_bot()
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: async def cmd_help(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
"""Help command - show available commands.""" """Help command - show available commands."""
@@ -2924,73 +2909,6 @@ async def cmd_deladmin(update: Update, context: ContextTypes.DEFAULT_TYPE) -> No
await update.message.reply_text(f"✅ Удалены: {', '.join(removed)}") await update.message.reply_text(f"✅ Удалены: {', '.join(removed)}")
# ============================================================================
# PROMO & CREDITS
# ============================================================================
def get_promo_text() -> str:
"""Return promo text with 2 hosters, optional YouTube link and donate."""
text = (
"<b>💰 Хостинг #1 — скидка до 60%</b>\n"
f"<a href='{PROMO_LINK_1}'>{PROMO_LINK_1}</a>\n\n"
"<b>Промокоды:</b>\n"
" <code>OFF60</code> — 60% на первый месяц\n"
" <code>BONUS20</code> — 20% + 3% за 3 мес\n"
" <code>BONUS6</code> — 15% + 5% за 6 мес\n\n"
"━━━━━━━━━━━━━━━━━━━━━━━━━\n\n"
"<b>💰 Хостинг #2 — скидка до 60%</b>\n"
f"<a href='{PROMO_LINK_2}'>{PROMO_LINK_2}</a>\n\n"
"<b>Промокод:</b>\n"
" <code>OFF60</code> — 60% на первый месяц\n\n"
"━━━━━━━━━━━━━━━━━━━━━━━━━\n\n"
"<b>☕ Донат / Чаевые</b>\n"
f"<a href='{TIP_LINK}'>{TIP_LINK}</a>"
)
if YOUTUBE_LINK:
text += (
"\n\n━━━━━━━━━━━━━━━━━━━━━━━━━\n\n"
"<b>▶ YouTube-канал</b>\n"
f"<a href='{html.escape(YOUTUBE_LINK)}'>{html.escape(YOUTUBE_LINK)}</a>"
)
return text
def should_show_promo_bot() -> bool:
"""Check if promo should be shown (once per 24h)."""
try:
if not os.path.exists(PROMO_STAMP_FILE):
return True
with open(PROMO_STAMP_FILE, "r") as f:
last_ts = int(f.read().strip())
return (int(time.time()) - last_ts) >= 86400
except (ValueError, OSError):
return True
def mark_promo_shown_bot() -> None:
"""Mark promo as shown."""
try:
os.makedirs(os.path.dirname(PROMO_STAMP_FILE), exist_ok=True)
with open(PROMO_STAMP_FILE, "w") as f:
f.write(str(int(time.time())))
except OSError:
pass
async def cb_menu_promo(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
"""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()
promo_msg = await query.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 cb_menu_credits(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None: async def cb_menu_credits(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
"""Credits and acknowledgements.""" """Credits and acknowledgements."""
query = update.callback_query query = update.callback_query
@@ -3180,7 +3098,6 @@ async def handle_callback(update: Update, context: ContextTypes.DEFAULT_TYPE) ->
"menu_update": cb_menu_update, "menu_update": cb_menu_update,
"menu_change": cb_menu_change, "menu_change": cb_menu_change,
"menu_website": cb_menu_website, "menu_website": cb_menu_website,
"menu_promo": cb_menu_promo,
"menu_credits": cb_menu_credits, "menu_credits": cb_menu_credits,
"menu_admin_web": cb_menu_admin_web, "menu_admin_web": cb_menu_admin_web,
"menu_admins": cb_menu_admins, "menu_admins": cb_menu_admins,

View File

@@ -31,7 +31,6 @@
"menu_restore": "↩️ Restore", "menu_restore": "↩️ Restore",
"menu_update": "📡 Update telemt", "menu_update": "📡 Update telemt",
"menu_website": "🌐 Website/SSL", "menu_website": "🌐 Website/SSL",
"menu_promo": "🎁 Promo",
"menu_stats": "📊 Traffic Stats", "menu_stats": "📊 Traffic Stats",
"menu_users": "🔑 Keys", "menu_users": "🔑 Keys",
"menu_admin_web": "🖥 Web Admin", "menu_admin_web": "🖥 Web Admin",
@@ -104,7 +103,6 @@
"admins_list": "Current admin IDs:", "admins_list": "Current admin IDs:",
"admins_empty": "No admins configured", "admins_empty": "No admins configured",
"promo_title": "🎁 Promo",
"credits_title": " Credits", "credits_title": " Credits",
"cg_title": "🔗 Custom Git Template", "cg_title": "🔗 Custom Git Template",

View File

@@ -31,7 +31,6 @@
"menu_restore": "↩️ Восстановить", "menu_restore": "↩️ Восстановить",
"menu_update": "📡 Обновить telemt", "menu_update": "📡 Обновить telemt",
"menu_website": "🌐 Сайт/SSL", "menu_website": "🌐 Сайт/SSL",
"menu_promo": "🎁 Промо",
"menu_stats": "📊 Трафик", "menu_stats": "📊 Трафик",
"menu_users": "🔑 Ключи", "menu_users": "🔑 Ключи",
"menu_admin_web": "🖥 Веб-админка", "menu_admin_web": "🖥 Веб-админка",
@@ -104,7 +103,6 @@
"admins_list": "Текущие ID админов:", "admins_list": "Текущие ID админов:",
"admins_empty": "Админы не настроены", "admins_empty": "Админы не настроены",
"promo_title": "🎁 Промо",
"credits_title": " О проекте", "credits_title": " О проекте",
"cg_title": "🔗 Свой git-шаблон", "cg_title": "🔗 Свой git-шаблон",