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",
ariaTrafficRange: "Traffic range",
ariaTrafficView: "Traffic view",
promoEyebrow: "Promo",
promoTitle: "Support PCAtelegram_web",
promoHosting1: "Hosting #1",
promoHosting2: "Hosting #2",
promoTips: "Tips",
qrEyebrow: "QR import",
qrTitle: "Scan Telegram proxy",
pageDashboardTitle: "Dashboard",
@@ -448,11 +443,6 @@ const i18n = {
ariaTrafficHistory: "История трафика",
ariaTrafficRange: "Период трафика",
ariaTrafficView: "Вид трафика",
promoEyebrow: "Промо",
promoTitle: "Поддержать PCAtelegram_web",
promoHosting1: "Хостинг #1",
promoHosting2: "Хостинг #2",
promoTips: "Чаевые",
qrEyebrow: "QR-импорт",
qrTitle: "Сканирование прокси Telegram",
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) => {
const nav = eventObj.target.closest("[data-nav]");
if (nav) {
@@ -1768,9 +1749,6 @@ document.addEventListener("submit", (eventObj) => {
$("#refreshBtn").addEventListener("click", refreshAll);
$("#autoRefreshToggle").addEventListener("click", () => setAutoRefresh(!state.autoRefreshEnabled));
$("#languageSelect").addEventListener("change", (eventObj) => setLanguage(eventObj.target.value));
$("#promoClose").addEventListener("click", () => {
$("#promoModal").hidden = true;
});
$("#qrClose").addEventListener("click", () => {
$("#qrModal").hidden = true;
});
@@ -1790,4 +1768,3 @@ renderEvents();
syncAutoRefreshTimer();
refreshAll();
loadLogs();
maybeShowPromo();

View File

@@ -376,8 +376,8 @@
</div>
<div id="toast" class="toast"></div>
<div id="qrModal" class="promo-modal" hidden>
<div class="promo-card qr-card" role="dialog" aria-modal="true" aria-labelledby="qrTitle">
<div id="qrModal" class="modal-overlay" hidden>
<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>
<p class="eyebrow" data-i18n="qrEyebrow">QR import</p>
<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>
</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>
</body>
</html>

View File

@@ -1362,7 +1362,7 @@ td small {
transform: translateY(0);
}
.promo-modal {
.modal-overlay {
position: fixed;
inset: 0;
z-index: 30;
@@ -1372,11 +1372,11 @@ td small {
background: rgba(5, 8, 16, .48);
}
.promo-modal[hidden] {
.modal-overlay[hidden] {
display: none;
}
.promo-card {
.modal-card {
position: relative;
width: min(620px, 100%);
border: 1px solid var(--line);
@@ -1386,40 +1386,12 @@ td small {
padding: 22px;
}
.promo-card .icon-btn {
.modal-card .icon-btn {
position: absolute;
top: 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) {
.keys-table {
min-width: 0;
@@ -1536,8 +1508,7 @@ td small {
}
@media (max-width: 720px) {
.visual-overview,
.promo-grid {
.visual-overview {
grid-template-columns: 1fr;
}

View File

@@ -215,7 +215,6 @@ submenu_about() {
echo -e " ${BOLD}${WHITE}$(t submenu_about_title)${NC}"
echo -e " ${DIM}$(printf '─%.0s' {1..54})${NC}"
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 " ${DIM}$(printf '─%.0s' {1..54})${NC}"
echo -ne " ${WHITE}$(t choose):${NC} "
@@ -223,7 +222,6 @@ submenu_about() {
case "$ch" in
1) menu_version ;;
2) menu_promo ;;
0) break ;;
*) log_error "$(t invalid_choice)" ;;
esac
@@ -1407,89 +1405,6 @@ bot_remove() {
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_language_picker() {
# Show picker only if language not yet saved
@@ -1829,11 +1744,6 @@ main() {
check_os
check_disk_space 500
# Promo once per day
if should_show_promo; then
show_promo_with_qr
fi
while true; do
clear
show_main_menu

View File

@@ -73,7 +73,6 @@ I18N[manage_language]="Language / Язык"
# ── Submenu: About ──────────────────────────────────────────────────────
I18N[submenu_about_title]=" ABOUT"
I18N[about_version_info]="Version info"
I18N[about_promo]="Promo / Donate"
I18N[version_title]="🔍 Information"
I18N[version_label]="PCAtelegram_web:"
I18N[version_engine]="Engine:"
@@ -214,21 +213,6 @@ I18N[bot_status_colon]="Status:"
I18N[bot_access_colon]="Access:"
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 ───────────────────────────────────────────────────────────────
I18N[stats_title]="📊 Traffic statistics"
I18N[stats_module_missing]="Statistics module not loaded."

View File

@@ -73,7 +73,6 @@ I18N[manage_language]="Язык / Language"
# ── Submenu: About ──────────────────────────────────────────────────────
I18N[submenu_about_title]=" О ПРОГРАММЕ"
I18N[about_version_info]="Информация о версии"
I18N[about_promo]="Промо / Донат"
I18N[version_title]="🔍 Информация"
I18N[version_label]="PCAtelegram_web:"
I18N[version_engine]="Ядро:"
@@ -214,21 +213,6 @@ I18N[bot_status_colon]="Статус:"
I18N[bot_access_colon]="Доступ:"
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 ───────────────────────────────────────────────────────────────
I18N[stats_title]="📊 Статистика трафика"
I18N[stats_module_missing]="Модуль статистики не загружен."

View File

@@ -16,7 +16,6 @@ Production-quality Telegram bot for managing MTProxy (telemt engine) on Linux se
- telemt updates
- Website/SSL management
- Remove installation
- Promotional links
- **Template Browsing** - Browse categories → templates → preview → install
- **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"
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")
ENV_FILE = "/opt/pcatelegram_web-bot/.env"
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:
"""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."""
like temporary notices that should auto-cleanup."""
try:
await asyncio.sleep(delay)
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_promo"), callback_data="menu_promo"),
],
[
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_admin_web"), callback_data="menu_admin_web"),
],
[
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_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_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:
"""Start command - show main menu, promo once per day.
"""Start command - show main menu.
Если 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"
)
# Промо раз в сутки — сообщение само удаляется через 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:
"""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)}")
# ============================================================================
# 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:
"""Credits and acknowledgements."""
query = update.callback_query
@@ -3180,7 +3098,6 @@ async def handle_callback(update: Update, context: ContextTypes.DEFAULT_TYPE) ->
"menu_update": cb_menu_update,
"menu_change": cb_menu_change,
"menu_website": cb_menu_website,
"menu_promo": cb_menu_promo,
"menu_credits": cb_menu_credits,
"menu_admin_web": cb_menu_admin_web,
"menu_admins": cb_menu_admins,

View File

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

View File

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