#!/bin/bash
# GoTelegram MTProxy — всё в одном файле.
# Установка: curl -sL -H "Authorization: token TOKEN" https://raw.githubusercontent.com/anten-ka/gotelegram_pro/main/install.sh -o /usr/local/bin/gotelegram && chmod +x /usr/local/bin/gotelegram && gotelegram
# ── Цвета ────────────────────────────────────────────────────────────────────
RED='\033[0;31m'
GREEN='\033[0;32m'
CYAN='\033[0;36m'
YELLOW='\033[1;33m'
MAGENTA='\033[0;35m'
BLUE='\033[0;34m'
WHITE='\033[1;37m'
NC='\033[0m'
# ── Конфиг ───────────────────────────────────────────────────────────────────
CONTAINER_NAME="mtproto-proxy"
BOT_DIR="/opt/gotelegram-bot"
SERVICE_NAME="gotelegram-bot"
TIP_LINK="https://pay.cloudtips.ru/p/7410814f"
PROMO_LINK="https://vk.cc/ct29NQ"
# ── Проверка root ────────────────────────────────────────────────────────────
if [ "$EUID" -ne 0 ]; then
echo -e "${RED}Запустите с sudo / root.${NC}"
exit 1
fi
# ── Установка пакетов ────────────────────────────────────────────────────────
install_pkg() {
if command -v apt-get &>/dev/null; then
apt-get update -qq && apt-get install -y -qq "$@"
elif command -v dnf &>/dev/null; then
dnf install -y "$@" 2>/dev/null
elif command -v yum &>/dev/null; then
yum install -y "$@"
fi
}
install_base_deps() {
for cmd in curl docker; do
if ! command -v $cmd &>/dev/null; then
echo -e "${YELLOW}[*] Установка $cmd...${NC}"
if [ "$cmd" = "docker" ]; then
curl -fsSL https://get.docker.com | sh
systemctl enable --now docker
else
install_pkg $cmd
fi
fi
done
if ! command -v qrencode &>/dev/null; then
install_pkg qrencode 2>/dev/null || true
fi
if ! docker info &>/dev/null 2>&1; then
systemctl start docker 2>/dev/null || true
sleep 2
fi
}
# ── Утилиты ──────────────────────────────────────────────────────────────────
get_ip() {
local ip
ip=$(curl -s -4 --max-time 5 https://api.ipify.org 2>/dev/null \
|| curl -s -4 --max-time 5 https://icanhazip.com 2>/dev/null \
|| echo "0.0.0.0")
echo "$ip" | grep -Eo '([0-9]{1,3}\.){3}[0-9]{1,3}' | head -1
}
check_port() {
local port="$1"
# Если порт занят нашим контейнером — ОК
if docker ps --format '{{.Names}}' 2>/dev/null | grep -q "^${CONTAINER_NAME}$"; then
local hp
hp=$(docker inspect "$CONTAINER_NAME" --format='{{range $p,$c := .HostConfig.PortBindings}}{{(index $c 0).HostPort}} {{end}}' 2>/dev/null)
for p in $hp; do [ "$p" = "$port" ] && return 1; done
fi
# Проверяем через ss или netstat
local line
line=$(ss -tlnp 2>/dev/null | grep -E ":${port}\b" | head -1)
[ -z "$line" ] && line=$(netstat -tlnp 2>/dev/null | grep -E ":${port}\b" | head -1)
if [ -n "$line" ]; then
echo "$line"
return 0
fi
return 1
}
show_containers() {
local list
list=$(docker ps --format "{{.Names}}\t{{.Image}}\t{{.Ports}}" 2>/dev/null | grep -v "^${CONTAINER_NAME}")
if [ -n "$list" ]; then
echo -e "${CYAN} Другие контейнеры на сервере:${NC}"
echo "$list" | while IFS= read -r l; do echo " $l"; done
fi
}
proxy_is_running() {
docker ps --format '{{.Names}}' 2>/dev/null | grep -q "^${CONTAINER_NAME}$"
}
# ── Показать данные подключения ──────────────────────────────────────────────
show_config() {
if ! proxy_is_running; then
echo -e "${RED}Прокси не запущен! Выберите пункт 1 для установки.${NC}"
return
fi
local SECRET IP PORT LINK
SECRET=$(docker inspect "$CONTAINER_NAME" --format='{{range .Config.Cmd}}{{.}} {{end}}' 2>/dev/null | awk '{print $NF}')
IP=$(get_ip)
PORT=$(docker inspect "$CONTAINER_NAME" --format='{{range $p,$c := .HostConfig.PortBindings}}{{(index $c 0).HostPort}} {{end}}' 2>/dev/null | awk '{print $1}')
PORT=${PORT:-443}
LINK="tg://proxy?server=$IP&port=$PORT&secret=$SECRET"
echo ""
echo -e "${GREEN}=== ДАННЫЕ ПОДКЛЮЧЕНИЯ ===${NC}"
echo -e " IP: ${WHITE}$IP${NC}"
echo -e " Port: ${WHITE}$PORT${NC} (TCP + UDP)"
echo -e " Secret: ${WHITE}$SECRET${NC}"
echo -e " Ссылка: ${BLUE}$LINK${NC}"
echo ""
if command -v qrencode &>/dev/null; then
qrencode -t ANSIUTF8 "$LINK"
fi
show_containers
}
# ── ПРОМО ────────────────────────────────────────────────────────────────────
show_promo() {
clear
echo -e "${MAGENTA}╔══════════════════════════════════════════════════════════════╗${NC}"
echo -e "${MAGENTA}║ ХОСТИНГ СО СКИДКОЙ ДО -60% ОТ ANTEN-KA ║${NC}"
echo -e "${MAGENTA}╚══════════════════════════════════════════════════════════════╝${NC}"
echo -e "${CYAN} >>> Ссылка: $PROMO_LINK ${NC}"
echo -e "\n${MAGENTA}❖ ••••••••••••••••••• АКТУАЛЬНЫЕ ПРОМОКОДЫ •••••••••••••••••• ❖${NC}"
printf " ${YELLOW}%-12s${NC} : ${WHITE}%s${NC}\n" "OFF60" "Скидка 60% на ПЕРВЫЙ МЕСЯЦ"
printf " ${YELLOW}%-12s${NC} : ${WHITE}%s${NC}\n" "antenka20" "Буст 20% + 3% (оплата за 3 МЕС)"
printf " ${YELLOW}%-12s${NC} : ${WHITE}%s${NC}\n" "antenka6" "Буст 15% + 5% (оплата за 6 МЕС)"
printf " ${YELLOW}%-12s${NC} : ${WHITE}%s${NC}\n" "antenka12" "Буст 5% + 5% (оплата за 12 МЕС)"
echo -e "${MAGENTA}❖ •••••••••••••••••••••••••••••••••••••••••••••••••••••••••••• ❖${NC}"
if command -v qrencode &>/dev/null; then
qrencode -t ANSIUTF8 "$PROMO_LINK"
echo -e "${GREEN}Сканируйте QR для перехода на хостинг${NC}"
fi
echo "--------------------------------------------------------------"
read -p "Нажмите [ENTER] для возврата в меню..."
}
# ── 1) Установить / Обновить MTProxy ─────────────────────────────────────────
menu_install() {
clear
echo -e "${CYAN}╔══════════════════════════════════════════════════════════════╗${NC}"
echo -e "${CYAN}║ Выберите домен для маскировки (Fake TLS) ║${NC}"
echo -e "${CYAN}╚══════════════════════════════════════════════════════════════╝${NC}"
local domains=(
"google.com" "wikipedia.org" "habr.com" "github.com"
"coursera.org" "udemy.com" "medium.com" "stackoverflow.com"
"bbc.com" "cnn.com" "reuters.com" "nytimes.com"
"lenta.ru" "rbc.ru" "ria.ru" "kommersant.ru"
"stepik.org" "duolingo.com" "khanacademy.org" "ted.com"
)
for i in "${!domains[@]}"; do
printf " ${YELLOW}%2d)${NC} %-22s" "$((i+1))" "${domains[$i]}"
[[ $(( (i+1) % 2 )) -eq 0 ]] && echo ""
done
echo ""
local d_idx DOMAIN
read -p "Ваш выбор [1-${#domains[@]}]: " d_idx
DOMAIN=${domains[$((d_idx-1))]}
DOMAIN=${DOMAIN:-google.com}
echo -e " Домен: ${GREEN}$DOMAIN${NC}"
# ── Выбор порта с проверкой занятости ────────────────────────────────────
echo ""
echo -e "${CYAN}--- Выберите порт ---${NC}"
local busy_line
echo -n " 1) 443 (Рекомендуется) "
if busy_line=$(check_port 443); then
echo -e "${RED}[ЗАНЯТ: $busy_line]${NC}"
else
echo -e "${GREEN}[свободен]${NC}"
fi
echo -n " 2) 8443 "
if busy_line=$(check_port 8443); then
echo -e "${RED}[ЗАНЯТ: $busy_line]${NC}"
else
echo -e "${GREEN}[свободен]${NC}"
fi
echo -e " 3) Свой порт"
local p_choice PORT
read -p " Выбор: " p_choice
case $p_choice in
2) PORT=8443 ;;
3)
while true; do
read -p " Введите порт (1-65535): " PORT
[[ "$PORT" =~ ^[0-9]+$ ]] && (( PORT >= 1 && PORT <= 65535 )) && break
echo -e " ${RED}Неверный порт.${NC}"
done
;;
*) PORT=443 ;;
esac
# Финальная проверка выбранного порта
if busy_line=$(check_port "$PORT"); then
echo ""
echo -e " ${YELLOW}Порт $PORT занят:${NC}"
echo -e " ${RED}$busy_line${NC}"
echo -e " 1) Всё равно использовать (если это ваш процесс)"
echo -e " 2) Отмена"
local force_choice
read -p " Выбор: " force_choice
if [ "$force_choice" != "1" ]; then
echo -e " ${YELLOW}Отменено.${NC}"
read -p " Нажмите Enter..."
return
fi
fi
echo ""
echo -e "${YELLOW}[*] Настройка прокси (домен: $DOMAIN, порт: $PORT)...${NC}"
# Docker проверка
if ! docker info &>/dev/null 2>&1; then
echo -e "${RED}Docker не запущен!${NC}"
read -p "Нажмите Enter..."
return
fi
# Генерация secret
echo -e "${GREEN}[*] Генерация secret...${NC}"
local SECRET
SECRET=$(docker run --rm nineseconds/mtg:2 generate-secret --hex "$DOMAIN" 2>/dev/null)
if [ -z "$SECRET" ]; then
echo -e "${RED}Ошибка генерации secret.${NC}"
read -p "Нажмите Enter..."
return
fi
# Остановка старого
docker stop "$CONTAINER_NAME" &>/dev/null
docker rm "$CONTAINER_NAME" &>/dev/null
# Запуск с TCP + UDP (UDP для звонков)
echo -e "${GREEN}[*] Запуск контейнера (TCP + UDP)...${NC}"
docker run -d --name "$CONTAINER_NAME" --restart always \
-p "$PORT":"$PORT"/tcp \
-p "$PORT":"$PORT"/udp \
nineseconds/mtg:2 simple-run \
-n 1.1.1.1 -i prefer-ipv4 \
0.0.0.0:"$PORT" "$SECRET" > /dev/null 2>&1
if ! proxy_is_running; then
echo -e "${RED}Контейнер не запустился. Проверьте: docker logs $CONTAINER_NAME${NC}"
read -p "Нажмите Enter..."
return
fi
# Сохраняем конфиг
mkdir -p "$BOT_DIR"
cat > "$BOT_DIR/proxy.json" << CFGEOF
{"domain": "$DOMAIN", "port": "$PORT", "secret": "$SECRET"}
CFGEOF
clear
echo -e "${GREEN}Прокси установлен! (TCP + UDP, звонки поддержаны)${NC}"
show_config
read -p "Нажмите Enter для возврата в меню..."
}
# ── 3) Настроить Telegram-бот ─────────────────────────────────────────────────
menu_setup_bot() {
clear
echo -e "${CYAN}╔══════════════════════════════════════════════════════════════╗${NC}"
echo -e "${CYAN}║ Настройка Telegram-бота ║${NC}"
echo -e "${CYAN}╚══════════════════════════════════════════════════════════════╝${NC}"
# Проверка статуса
if systemctl is-active --quiet "$SERVICE_NAME" 2>/dev/null; then
echo -e " Статус бота: ${GREEN}работает${NC}"
echo ""
echo -e " 1) Обновить файлы бота и перезапустить"
echo -e " 2) Изменить BOT_TOKEN"
echo -e " 3) Остановить бота"
echo -e " 0) Назад"
local sub
read -p " Выбор: " sub
case $sub in
1)
write_bot_files
install_bot_deps
systemctl restart "$SERVICE_NAME"
echo -e "${GREEN}[*] Бот обновлён и перезапущен.${NC}"
;;
2)
echo -e "${YELLOW}Введите новый BOT_TOKEN:${NC}"
local tok
read -r tok
tok=$(echo "$tok" | tr -d '[:space:]')
if [ -n "$tok" ]; then
echo "BOT_TOKEN=$tok" > "$BOT_DIR/.env"
chmod 600 "$BOT_DIR/.env"
systemctl restart "$SERVICE_NAME"
echo -e "${GREEN}[*] Токен обновлён, бот перезапущен.${NC}"
else
echo -e "${RED}Пустой токен, отмена.${NC}"
fi
;;
3)
systemctl stop "$SERVICE_NAME"
echo -e "${YELLOW}Бот остановлен.${NC}"
;;
*) return ;;
esac
read -p "Нажмите Enter..."
return
fi
echo -e " Статус бота: ${RED}не установлен / не запущен${NC}"
echo ""
echo -e " Бот позволяет управлять MTProxy из Telegram:"
echo -e " установка, статус, ссылка, поделиться ключом и т.д."
echo ""
read -p " Установить бота? (y/n): " yn
[ "$yn" != "y" ] && [ "$yn" != "Y" ] && return
# Зависимости Python
echo -e "${GREEN}[*] Проверка Python...${NC}"
if ! command -v python3 &>/dev/null; then
install_pkg python3 python3-pip
fi
command -v python3 &>/dev/null || { echo -e "${RED}python3 не найден!${NC}"; read -p "Enter..."; return; }
local PY_VER
PY_VER=$(python3 -c "import sys; print(f'{sys.version_info.major}.{sys.version_info.minor}')" 2>/dev/null || echo "3")
if ! python3 -m venv --help &>/dev/null 2>&1; then
echo -e "${YELLOW}[*] Установка python${PY_VER}-venv...${NC}"
install_pkg "python${PY_VER}-venv" 2>/dev/null
install_pkg python3-venv 2>/dev/null
install_pkg python3-pip 2>/dev/null
python3 -m venv --help &>/dev/null 2>&1 || {
echo -e "${RED}Не удалось установить venv. Выполните: apt install python${PY_VER}-venv${NC}"
read -p "Enter..."; return
}
fi
# Файлы бота
write_bot_files
# venv + pip
install_bot_deps
# BOT_TOKEN
if [ ! -f "$BOT_DIR/.env" ]; then
echo ""
echo -e "${YELLOW}Введите BOT_TOKEN от @BotFather:${NC}"
local TOKEN=""
while [ -z "$TOKEN" ]; do
read -r TOKEN
TOKEN=$(echo "$TOKEN" | tr -d '[:space:]')
[ -z "$TOKEN" ] && echo -e "${RED}Токен не может быть пустым.${NC}"
done
echo "BOT_TOKEN=$TOKEN" > "$BOT_DIR/.env"
chmod 600 "$BOT_DIR/.env"
echo -e "${GREEN}[*] .env создан.${NC}"
else
echo -e "${GREEN}[*] .env уже есть — используем существующий.${NC}"
fi
# systemd
cat > "/etc/systemd/system/${SERVICE_NAME}.service" << EOF
[Unit]
Description=GoTelegram MTProxy Bot
After=network.target docker.service
[Service]
Type=simple
WorkingDirectory=$BOT_DIR
ExecStart=$BOT_DIR/venv/bin/python $BOT_DIR/bot.py
Restart=always
RestartSec=5
Environment=PATH=$BOT_DIR/venv/bin:/usr/bin:/usr/local/bin
[Install]
WantedBy=multi-user.target
EOF
systemctl daemon-reload
systemctl enable "$SERVICE_NAME" 2>/dev/null
systemctl restart "$SERVICE_NAME" 2>/dev/null || systemctl start "$SERVICE_NAME"
echo ""
echo -e "${GREEN}[*] Telegram-бот установлен и запущен!${NC}"
echo -e " Проверка: systemctl status $SERVICE_NAME"
echo -e " Логи: journalctl -u $SERVICE_NAME -f"
read -p " Нажмите Enter..."
}
write_bot_files() {
mkdir -p "$BOT_DIR"
cat > "$BOT_DIR/requirements.txt" << 'REQEOF'
python-telegram-bot>=21.0
REQEOF
cat > "$BOT_DIR/bot.py" << 'BOTEOF'
#!/usr/bin/env python3
import asyncio, html, json, os, re
from pathlib import Path
_env_path = Path(__file__).resolve().parent / ".env"
if _env_path.exists():
with open(_env_path, encoding="utf-8") as f:
for line in f:
line = line.strip()
if line and not line.startswith("#") and "=" in line:
k, v = line.split("=", 1)
os.environ.setdefault(k.strip(), v.strip().strip('"').strip("'"))
from telegram import Update, InlineKeyboardButton, InlineKeyboardMarkup
from telegram.ext import Application, CommandHandler, CallbackQueryHandler, ContextTypes, MessageHandler, filters
BOT_TOKEN = os.environ.get("BOT_TOKEN")
_allowed = os.environ.get("ALLOWED_IDS", "").strip()
try:
ALLOWED_IDS = set(int(x) for x in _allowed.split(",") if x.strip()) if _allowed else None
except ValueError:
ALLOWED_IDS = None
CONTAINER_NAME = "mtproto-proxy"
CONFIG_FILE = Path("/opt/gotelegram-bot/proxy.json")
DOMAINS = [
"google.com","wikipedia.org","habr.com","github.com",
"coursera.org","udemy.com","medium.com","stackoverflow.com",
"bbc.com","cnn.com","reuters.com","nytimes.com",
"lenta.ru","rbc.ru","ria.ru","kommersant.ru",
"stepik.org","duolingo.com","khanacademy.org","ted.com",
]
PROMO_LINK = "https://vk.cc/ct29NQ"
TIP_LINK = "https://pay.cloudtips.ru/p/7410814f"
def _ok(uid):
return ALLOWED_IDS is None or uid in ALLOWED_IDS
def _decode(data):
return (data or b"").decode("utf-8", errors="replace").strip()
async def sh(*args, timeout=60):
proc = await asyncio.create_subprocess_exec(*args, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE)
try:
out, err = await asyncio.wait_for(proc.communicate(), timeout=timeout)
except asyncio.TimeoutError:
proc.kill(); await proc.wait(); return -1, "", "Timeout"
return proc.returncode or 0, _decode(out), _decode(err)
async def get_ip():
for url in ("https://api.ipify.org","https://icanhazip.com","https://ifconfig.me"):
code, out, _ = await sh("curl","-s","-4","--max-time","5",url, timeout=8)
if code == 0 and out:
m = re.search(r"(\d{1,3}\.){3}\d{1,3}", out)
if m: return m.group(0)
return "0.0.0.0"
async def proxy_running():
code, out, _ = await sh("docker","ps","--format","{{.Names}}", timeout=10)
return code == 0 and CONTAINER_NAME in out
async def docker_val(fmt):
code, out, _ = await sh("docker","inspect",CONTAINER_NAME,"--format",fmt, timeout=10)
return out.strip() if code == 0 else ""
async def check_port(port):
if await proxy_running():
hp = await docker_val("{{range $p,$c := .HostConfig.PortBindings}}{{(index $c 0).HostPort}} {{end}}")
if str(port) in hp.split(): return None
code, out, _ = await sh("ss","-tlnp", timeout=5)
if code != 0: code, out, _ = await sh("netstat","-tlnp", timeout=5)
for line in out.splitlines():
if f":{port} " in line or f":{port}\t" in line: return line
return None
async def docker_containers_info():
code, out, _ = await sh("docker","ps","--format","{{.Names}}\t{{.Image}}\t{{.Ports}}", timeout=10)
return out if code == 0 else ""
def save_config(data):
CONFIG_FILE.parent.mkdir(parents=True, exist_ok=True)
CONFIG_FILE.write_text(json.dumps(data, ensure_ascii=False, indent=2), encoding="utf-8")
def load_config():
if CONFIG_FILE.exists():
try: return json.loads(CONFIG_FILE.read_text(encoding="utf-8"))
except Exception: pass
return {}
async def proxy_info():
if not await proxy_running(): return None
cmd_str = await docker_val("{{range .Config.Cmd}}{{.}} {{end}}")
secret = cmd_str.split()[-1] if cmd_str else ""
hp = await docker_val("{{range $p,$c := .HostConfig.PortBindings}}{{(index $c 0).HostPort}} {{end}}")
port = hp.split()[0] if hp.strip() else "443"
ip = await get_ip()
link = f"tg://proxy?server={ip}&port={port}&secret={secret}"
cfg = load_config()
return {"ip":ip,"port":port,"secret":secret,"link":link,"domain":cfg.get("domain","—")}
def main_menu_kb():
return InlineKeyboardMarkup([
[InlineKeyboardButton("🔧 Установить / Обновить", callback_data="menu_install")],
[InlineKeyboardButton("📊 Статус", callback_data="menu_status"),
InlineKeyboardButton("🔗 Ссылка", callback_data="menu_link")],
[InlineKeyboardButton("📤 Поделиться ключом", callback_data="menu_share")],
[InlineKeyboardButton("🔄 Перезапуск", callback_data="menu_restart"),
InlineKeyboardButton("📋 Логи", callback_data="menu_logs")],
[InlineKeyboardButton("🗑 Удалить", callback_data="menu_remove"),
InlineKeyboardButton("🏷 Промо", callback_data="menu_promo")],
])
HELP_TEXT = (
"🚀 GoTelegram MTProxy Bot\n\n"
"Управление MTProxy (Fake TLS) на сервере.\n"
"TCP + UDP (звонки) поддержаны.\n\n"
"Используйте кнопки ниже или команды:\n"
"/install /status /link /share /restart /logs /remove /promo"
)
async def start(update, ctx):
if not update.effective_user: return
if not _ok(update.effective_user.id):
msg = update.message or (update.callback_query and update.callback_query.message)
if msg: await msg.reply_text("⛔ Доступ запрещён.")
return
if update.message:
await update.message.reply_text(HELP_TEXT, parse_mode="HTML", reply_markup=main_menu_kb())
elif update.callback_query:
await update.callback_query.edit_message_text(HELP_TEXT, parse_mode="HTML", reply_markup=main_menu_kb())
async def cmd_status(update, ctx):
msg = update.message or (update.callback_query and update.callback_query.message)
if not update.effective_user or not msg: return
if not _ok(update.effective_user.id): await msg.reply_text("⛔"); return
info = await proxy_info()
if not info:
text = "❌ Прокси не запущен.\nНажмите Установить."
else:
containers = await docker_containers_info()
other = "\n".join(l for l in containers.splitlines() if CONTAINER_NAME not in l)
text = ("✅ Прокси работает\n\n"
f"IP: {html.escape(info['ip'])}\n"
f"Порт: {html.escape(info['port'])}\n"
f"Домен: {html.escape(info['domain'])}\n"
f"Secret: {html.escape(info['secret'])}\n\n"
f"Ссылка:\n{html.escape(info['link'])}")
if other:
text += f"\n\n📦 Другие контейнеры:\n
{html.escape(other)}"
kb = InlineKeyboardMarkup([[InlineKeyboardButton("◀️ Меню", callback_data="menu_main")]])
if update.callback_query:
await update.callback_query.edit_message_text(text, parse_mode="HTML", reply_markup=kb)
else:
await msg.reply_text(text, parse_mode="HTML", reply_markup=kb)
async def cmd_link(update, ctx):
msg = update.message or (update.callback_query and update.callback_query.message)
if not update.effective_user or not msg: return
if not _ok(update.effective_user.id): return
info = await proxy_info()
text = f"{html.escape(info['link'])}" if info else "❌ Прокси не запущен."
kb = InlineKeyboardMarkup([[InlineKeyboardButton("◀️ Меню", callback_data="menu_main")]])
if update.callback_query:
await update.callback_query.edit_message_text(text, parse_mode="HTML", reply_markup=kb)
else:
await msg.reply_text(text, parse_mode="HTML", reply_markup=kb)
async def cmd_share(update, ctx):
msg = update.message or (update.callback_query and update.callback_query.message)
if not update.effective_user or not msg: return
if not _ok(update.effective_user.id): return
info = await proxy_info()
if not info:
kb = InlineKeyboardMarkup([[InlineKeyboardButton("◀️ Меню", callback_data="menu_main")]])
if update.callback_query: await update.callback_query.edit_message_text("❌ Прокси не запущен.", reply_markup=kb)
else: await msg.reply_text("❌ Прокси не запущен.", reply_markup=kb)
return
tg_link = info["link"]
share_text = (
f"🔐 MTProxy для Telegram\n\n"
f"🌍 Сервер: {html.escape(info['ip'])}\n"
f"🔌 Порт: {html.escape(info['port'])}\n"
f"🔑 Secret: {html.escape(info['secret'])}\n\n"
f"👉 Подключиться одним нажатием:\n"
f"{html.escape(tg_link)}\n\n"
f"Просто нажмите на ссылку или перешлите это сообщение.")
kb = InlineKeyboardMarkup([
[InlineKeyboardButton("📤 Переслать другу", switch_inline_query=tg_link)],
[InlineKeyboardButton("◀️ Меню", callback_data="menu_main")],
])
if update.callback_query:
await update.callback_query.edit_message_text(share_text, parse_mode="HTML", reply_markup=kb)
else:
await msg.reply_text(share_text, parse_mode="HTML", reply_markup=kb)
async def cmd_remove(update, ctx):
msg = update.message or (update.callback_query and update.callback_query.message)
if not update.effective_user or not msg: return
if not _ok(update.effective_user.id): return
chat = msg.chat
if update.callback_query: await update.callback_query.edit_message_text("⏳ Удаляю прокси...")
else: await chat.send_message("⏳ Удаляю прокси...")
await sh("docker","stop",CONTAINER_NAME, timeout=15)
await sh("docker","rm",CONTAINER_NAME, timeout=10)
text = "✅ Прокси удалён." if not await proxy_running() else "⚠️ Не удалось удалить."
kb = InlineKeyboardMarkup([[InlineKeyboardButton("◀️ Меню", callback_data="menu_main")]])
await chat.send_message(text, reply_markup=kb)
async def cmd_restart(update, ctx):
msg = update.message or (update.callback_query and update.callback_query.message)
if not update.effective_user or not msg: return
if not _ok(update.effective_user.id): return
if not await proxy_running():
kb = InlineKeyboardMarkup([[InlineKeyboardButton("◀️ Меню", callback_data="menu_main")]])
if update.callback_query: await update.callback_query.edit_message_text("❌ Прокси не запущен.", reply_markup=kb)
else: await msg.reply_text("❌ Прокси не запущен.", reply_markup=kb)
return
chat = msg.chat
if update.callback_query: await update.callback_query.edit_message_text("⏳ Перезапуск...")
code, _, err = await sh("docker","restart",CONTAINER_NAME, timeout=30)
text = "✅ Перезапущен." if code == 0 else f"❌ Ошибка: {err or 'unknown'}"
kb = InlineKeyboardMarkup([[InlineKeyboardButton("◀️ Меню", callback_data="menu_main")]])
await chat.send_message(text, reply_markup=kb)
async def cmd_logs(update, ctx):
msg = update.message or (update.callback_query and update.callback_query.message)
if not update.effective_user or not msg: return
if not _ok(update.effective_user.id): return
if not await proxy_running():
kb = InlineKeyboardMarkup([[InlineKeyboardButton("◀️ Меню", callback_data="menu_main")]])
if update.callback_query: await update.callback_query.edit_message_text("❌ Прокси не запущен.", reply_markup=kb)
else: await msg.reply_text("❌ Прокси не запущен.", reply_markup=kb)
return
code, out, err = await sh("docker","logs","--tail","40",CONTAINER_NAME, timeout=15)
text = (out or "") + (("\n" + err) if err else "") or "Нет вывода."
if len(text) > 4000: text = text[-4000:]
kb = InlineKeyboardMarkup([[InlineKeyboardButton("◀️ Меню", callback_data="menu_main")]])
if update.callback_query:
await update.callback_query.edit_message_text(f"{html.escape(text)}", parse_mode="HTML", reply_markup=kb)
else:
await msg.reply_text(f"{html.escape(text)}", parse_mode="HTML", reply_markup=kb)
async def cmd_promo(update, ctx):
msg = update.message or (update.callback_query and update.callback_query.message)
if not update.effective_user or not msg: return
if not _ok(update.effective_user.id): return
text = ("💰 Хостинг со скидкой до -60%\n"
f"Ссылка: {PROMO_LINK}\n\nПромокоды: OFF60, antenka20, antenka6, antenka12\n\nДонат: {TIP_LINK}")
kb = InlineKeyboardMarkup([[InlineKeyboardButton("◀️ Меню", callback_data="menu_main")]])
if update.callback_query:
await update.callback_query.edit_message_text(text, parse_mode="HTML", reply_markup=kb)
else:
await msg.reply_text(text, parse_mode="HTML", reply_markup=kb)
async def install_step_domain(update, ctx):
msg = update.message or (update.callback_query and update.callback_query.message)
if not update.effective_user or not msg: return
if not _ok(update.effective_user.id): return
buttons, row = [], []
for i, d in enumerate(DOMAINS):
row.append(InlineKeyboardButton(d, callback_data=f"dom_{i}"))
if len(row) == 2: buttons.append(row); row = []
if row: buttons.append(row)
text = "🌐 Выберите домен для маскировки (Fake TLS):"
if update.callback_query:
await update.callback_query.edit_message_text(text, parse_mode="HTML", reply_markup=InlineKeyboardMarkup(buttons))
else:
await msg.reply_text(text, parse_mode="HTML", reply_markup=InlineKeyboardMarkup(buttons))
async def install_step_port(update, ctx):
query = update.callback_query
domain = ctx.user_data.get("install_domain", "google.com")
busy_443 = await check_port(443)
busy_8443 = await check_port(8443)
rows = []
l443 = "443 (рекомендуется)" if not busy_443 else "443 ⚠️ занят"
l8443 = "8443" if not busy_8443 else "8443 ⚠️ занят"
rows.append([InlineKeyboardButton(l443, callback_data="port_443"), InlineKeyboardButton(l8443, callback_data="port_8443")])
rows.append([InlineKeyboardButton("◀️ Меню", callback_data="menu_main")])
pi = ""
if busy_443: pi += f"\n⚠️ Порт 443 занят:\n{html.escape(busy_443[:300])}\n"
if busy_8443: pi += f"\n⚠️ Порт 8443 занят:\n{html.escape(busy_8443[:300])}\n"
text = f"Домен: {html.escape(domain)}\n\n🔌 Выберите порт или введите свой (1-65535):{pi}"
ctx.user_data["install_wait_port"] = True
await query.edit_message_text(text, parse_mode="HTML", reply_markup=InlineKeyboardMarkup(rows))
async def install_port_chosen(update, ctx, port_str):
port = int(port_str)
msg = update.callback_query.message if update.callback_query else update.message
if not msg: return
chat = msg.chat
busy = await check_port(port)
if busy:
kb = InlineKeyboardMarkup([
[InlineKeyboardButton(f"Всё равно использовать {port}", callback_data=f"force_{port}")],
[InlineKeyboardButton("Выбрать другой порт", callback_data="reselect_port")],
[InlineKeyboardButton("◀️ Меню", callback_data="menu_main")],
])
text = f"⚠️ Порт {port} занят!\n\n{html.escape(busy[:500])}\n\nМожно использовать всё равно или выбрать другой."
if update.callback_query: await update.callback_query.edit_message_text(text, parse_mode="HTML", reply_markup=kb)
else: await chat.send_message(text, parse_mode="HTML", reply_markup=kb)
ctx.user_data["install_port"] = port_str
return
ctx.user_data["install_port"] = port_str
ctx.user_data["install_wait_port"] = False
await do_install(update, ctx)
async def do_install(update, ctx):
domain = ctx.user_data.get("install_domain") or "google.com"
port = ctx.user_data.get("install_port") or "443"
if update.callback_query:
msg = update.callback_query.message
await msg.edit_text("⏳ Генерация secret и запуск контейнера...", reply_markup=None)
elif update.message:
msg = update.message
await msg.reply_text("⏳ Генерация secret и запуск контейнера...")
else: return
chat = msg.chat
code, _, _ = await sh("docker","info", timeout=10)
if code != 0:
await chat.send_message("❌ Docker не запущен.", parse_mode="HTML",
reply_markup=InlineKeyboardMarkup([[InlineKeyboardButton("◀️ Меню", callback_data="menu_main")]]))
return
code, secret_out, err = await sh("docker","run","--rm","nineseconds/mtg:2","generate-secret","--hex",domain, timeout=60)
if code != 0: await chat.send_message(f"❌ Генерация secret: {err or secret_out}"); return
secret = secret_out.strip().split()[-1] if secret_out.strip() else ""
if not secret: await chat.send_message("❌ Пустой secret."); return
await sh("docker","stop",CONTAINER_NAME, timeout=15)
await sh("docker","rm",CONTAINER_NAME, timeout=10)
code, _, err = await sh("docker","run","-d","--name",CONTAINER_NAME,"--restart","always",
"-p",f"{port}:{port}/tcp","-p",f"{port}:{port}/udp",
"nineseconds/mtg:2","simple-run","-n","1.1.1.1","-i","prefer-ipv4",
f"0.0.0.0:{port}",secret, timeout=90)
if code != 0: await chat.send_message(f"❌ Запуск контейнера: {err}"); return
save_config({"domain":domain,"port":port,"secret":secret})
ip = await get_ip()
link = f"tg://proxy?server={ip}&port={port}&secret={secret}"
text = ("✅ Прокси установлен!\n\n"
f"🌍 IP: {html.escape(ip)}\n"
f"🔌 Порт: {html.escape(port)} (TCP + UDP)\n"
f"🎭 Домен: {html.escape(domain)}\n"
f"🔑 Secret: {html.escape(secret)}\n\n"
f"👉 Ссылка:\n{html.escape(link)}\n\n📞 Звонки поддержаны (UDP).")
kb = InlineKeyboardMarkup([
[InlineKeyboardButton("📤 Поделиться ключом", callback_data="menu_share")],
[InlineKeyboardButton("◀️ Меню", callback_data="menu_main")],
])
await chat.send_message(text, parse_mode="HTML", reply_markup=kb)
for k in ("install_domain","install_port","install_wait_port"): ctx.user_data.pop(k, None)
async def callback_handler(update, ctx):
query = update.callback_query
if not query or not update.effective_user: return
await query.answer()
if not _ok(update.effective_user.id): await query.edit_message_text("⛔ Доступ запрещён."); return
data = query.data or ""
if data == "menu_main": await start(update, ctx)
elif data == "menu_install": await install_step_domain(update, ctx)
elif data == "menu_status": await cmd_status(update, ctx)
elif data == "menu_link": await cmd_link(update, ctx)
elif data == "menu_share": await cmd_share(update, ctx)
elif data == "menu_restart": await cmd_restart(update, ctx)
elif data == "menu_logs": await cmd_logs(update, ctx)
elif data == "menu_remove": await cmd_remove(update, ctx)
elif data == "menu_promo": await cmd_promo(update, ctx)
elif data.startswith("dom_"):
try: idx = int(data[4:])
except ValueError: await query.edit_message_text("❌ Ошибка."); return
if not (0 <= idx < len(DOMAINS)): await query.edit_message_text("❌ Неверный выбор."); return
ctx.user_data["install_domain"] = DOMAINS[idx]
await install_step_port(update, ctx)
elif data == "port_443": await install_port_chosen(update, ctx, "443")
elif data == "port_8443": await install_port_chosen(update, ctx, "8443")
elif data.startswith("force_"):
ctx.user_data["install_port"] = data[6:]
ctx.user_data["install_wait_port"] = False
await do_install(update, ctx)
elif data == "reselect_port": await install_step_port(update, ctx)
async def text_handler(update, ctx):
if not update.message or not ctx.user_data.get("install_wait_port"): return
text = (update.message.text or "").strip()
if not re.match(r"^\d+$", text): return
port = int(text)
if not (1 <= port <= 65535): await update.message.reply_text("Введите число от 1 до 65535."); return
await install_port_chosen(update, ctx, str(port))
def main():
if not BOT_TOKEN: raise SystemExit("Задайте BOT_TOKEN в .env")
app = Application.builder().token(BOT_TOKEN).build()
app.add_handler(CommandHandler("start", start))
app.add_handler(CommandHandler("help", start))
app.add_handler(CommandHandler("install", install_step_domain))
app.add_handler(CommandHandler("status", cmd_status))
app.add_handler(CommandHandler("link", cmd_link))
app.add_handler(CommandHandler("share", cmd_share))
app.add_handler(CommandHandler("remove", cmd_remove))
app.add_handler(CommandHandler("restart", cmd_restart))
app.add_handler(CommandHandler("logs", cmd_logs))
app.add_handler(CommandHandler("promo", cmd_promo))
app.add_handler(CallbackQueryHandler(callback_handler))
app.add_handler(MessageHandler(filters.TEXT & ~filters.COMMAND, text_handler))
app.run_polling(allowed_updates=Update.ALL_TYPES)
if __name__ == "__main__":
main()
BOTEOF
}
install_bot_deps() {
echo -e "${GREEN}[*] Настройка Python venv...${NC}"
if [ ! -d "$BOT_DIR/venv" ]; then
python3 -m venv "$BOT_DIR/venv" || { echo -e "${RED}Ошибка создания venv.${NC}"; return 1; }
fi
echo -e "${GREEN}[*] Установка зависимостей (pip)...${NC}"
"$BOT_DIR/venv/bin/pip" install --upgrade pip -q 2>/dev/null || true
"$BOT_DIR/venv/bin/pip" install -r "$BOT_DIR/requirements.txt" -q || { echo -e "${RED}pip install не удался.${NC}"; return 1; }
}
# ── Выход ────────────────────────────────────────────────────────────────────
show_exit() {
clear
show_config
echo ""
echo -e "${MAGENTA}Поддержка автора (CloudTips):${NC}"
echo -e " $TIP_LINK"
echo -e " YouTube: https://www.youtube.com/@antenkaru"
if command -v qrencode &>/dev/null; then
qrencode -t ANSIUTF8 "$TIP_LINK"
fi
exit 0
}
# ══════════════════════════════════════════════════════════════════════════════
# ██ СТАРТ СКРИПТА
# ══════════════════════════════════════════════════════════════════════════════
install_base_deps
# Копируем себя в /usr/local/bin/gotelegram (если запущены из другого места)
SELF="$(realpath "$0")"
if [ "$SELF" != "/usr/local/bin/gotelegram" ]; then
cp "$SELF" /usr/local/bin/gotelegram && chmod +x /usr/local/bin/gotelegram
fi
show_promo
# ── Главное меню (цикл) ─────────────────────────────────────────────────────
while true; do
echo ""
echo -e "${MAGENTA}╔══════════════════════════════════════════════════════════════╗${NC}"
echo -e "${MAGENTA}║ GoTelegram Manager (by anten-ka) ║${NC}"
echo -e "${MAGENTA}╚══════════════════════════════════════════════════════════════╝${NC}"
# Статус прокси
if proxy_is_running; then
echo -e " Прокси: ${GREEN}работает${NC}"
else
echo -e " Прокси: ${RED}не запущен${NC}"
fi
# Статус бота
if systemctl is-active --quiet "$SERVICE_NAME" 2>/dev/null; then
echo -e " Telegram-бот: ${GREEN}работает${NC}"
else
echo -e " Telegram-бот: ${YELLOW}не настроен${NC}"
fi
echo ""
echo -e " ${GREEN}1)${NC} Установить / Обновить прокси"
echo -e " ${GREEN}2)${NC} Показать данные подключения"
echo -e " ${CYAN}3)${NC} Настроить Telegram-бот"
echo -e " ${GREEN}4)${NC} Перезапустить прокси"
echo -e " ${GREEN}5)${NC} Логи прокси"
echo -e " ${YELLOW}6)${NC} Показать PROMO"
echo -e " ${RED}7)${NC} Удалить прокси"
echo -e " ${WHITE}0)${NC} Выход"
echo ""
read -p " Пункт: " m_idx
case $m_idx in
1) menu_install ;;
2) clear; show_config; read -p "Нажмите Enter..." ;;
3) menu_setup_bot ;;
4)
if proxy_is_running; then
docker restart "$CONTAINER_NAME" && echo -e "${GREEN}Перезапущен.${NC}" || echo -e "${RED}Ошибка.${NC}"
else
echo -e "${RED}Прокси не запущен.${NC}"
fi
read -p "Нажмите Enter..."
;;
5)
if proxy_is_running; then
docker logs --tail 50 "$CONTAINER_NAME" 2>&1
else
echo -e "${RED}Прокси не запущен.${NC}"
fi
read -p "Нажмите Enter..."
;;
6) show_promo ;;
7)
if proxy_is_running; then
docker stop "$CONTAINER_NAME" &>/dev/null
docker rm "$CONTAINER_NAME" &>/dev/null
echo -e "${GREEN}Прокси удалён.${NC}"
else
echo -e "${YELLOW}Прокси не был запущен.${NC}"
fi
read -p "Нажмите Enter..."
;;
0) show_exit ;;
*) echo -e "${RED}Неверный ввод.${NC}" ;;
esac
done