mirror of
https://github.com/andrey271192/PCAtelegram_web.git
synced 2026-09-20 11:55:32 +00:00
chore: remove promo surfaces
This commit is contained in:
@@ -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();
|
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
90
install.sh
90
install.sh
@@ -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
|
||||||
|
|||||||
@@ -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."
|
||||||
|
|||||||
@@ -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]="Модуль статистики не загружен."
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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-шаблон",
|
||||||
|
|||||||
Reference in New Issue
Block a user