import os import io import random import logging import sqlite3 from datetime import datetime from threading import Thread import aiohttp from flask import Flask from telegram import Update, InlineKeyboardButton, InlineKeyboardMarkup from telegram.ext import ( Application, CommandHandler, CallbackQueryHandler, MessageHandler, ContextTypes, filters ) # ====== ЛОГИ ====== logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s") log = logging.getLogger("moti-vpn") # ====== НАСТРОЙКИ ====== BOT_TOKEN = os.environ.get("botvpntoken") if not BOT_TOKEN: raise SystemExit("❌ Не найден ENV botvpntoken") # База внутри /app/data DB_PATH = os.path.join(os.path.dirname(__file__), "data", "bot.db") os.makedirs(os.path.dirname(DB_PATH), exist_ok=True) PORT = int(os.environ.get("PORT", "7860")) SUBSCRIPTION_URLS = [ "https://raw.githubusercontent.com/barry-far/V2ray-config/main/Sub1.txt", "https://raw.githubusercontent.com/barry-far/V2ray-config/main/Sub2.txt", "https://raw.githubusercontent.com/barry-far/V2ray-config/main/Sub3.txt", "https://raw.githubusercontent.com/barry-far/V2ray-config/main/Sub4.txt", "https://raw.githubusercontent.com/barry-far/V2ray-config/main/Sub5.txt", "https://raw.githubusercontent.com/barry-far/V2ray-config/main/Sub6.txt", "https://raw.githubusercontent.com/barry-far/V2ray-config/main/Sub7.txt", "https://raw.githubusercontent.com/barry-far/V2ray-config/main/Sub8.txt", ] PROTOCOL_URLS = { "vmess": "https://raw.githubusercontent.com/barry-far/V2ray-config/main/Splitted-By-Protocol/vmess.txt", "vless": "https://raw.githubusercontent.com/barry-far/V2ray-config/main/Splitted-By-Protocol/vless.txt", "trojan": "https://raw.githubusercontent.com/barry-far/V2ray-config/main/Splitted-By-Protocol/trojan.txt", "ss": "https://raw.githubusercontent.com/barry-far/V2ray-config/main/Splitted-By-Protocol/ss.txt", "ssr": "https://raw.githubusercontent.com/barry-far/V2ray-config/main/Splitted-By-Protocol/ssr.txt", } # ====== SQLITE ====== def db(): conn = sqlite3.connect(DB_PATH) conn.row_factory = sqlite3.Row return conn def init_db(): with db() as c: c.execute("""CREATE TABLE IF NOT EXISTS users ( tg_id INTEGER PRIMARY KEY, username TEXT, first_name TEXT, created_at TEXT, custom_key TEXT )""") c.execute("""CREATE TABLE IF NOT EXISTS events ( id INTEGER PRIMARY KEY AUTOINCREMENT, tg_id INTEGER, type TEXT, payload TEXT, created_at TEXT )""") c.commit() def ensure_user(u): with db() as c: cur = c.execute("SELECT tg_id FROM users WHERE tg_id=?", (u.id,)) if not cur.fetchone(): c.execute("INSERT INTO users (tg_id, username, first_name, created_at) VALUES (?,?,?,?)", (u.id, u.username, u.first_name, datetime.utcnow().isoformat())) c.commit() def set_key(tg_id, val): with db() as c: c.execute("UPDATE users SET custom_key=? WHERE tg_id=?", (val, tg_id)) c.commit() def get_key(tg_id): with db() as c: cur = c.execute("SELECT custom_key FROM users WHERE tg_id=?", (tg_id,)) row = cur.fetchone() return row[0] if row else None # ====== HTTP health ====== flask_app = Flask(__name__) @flask_app.get("/") @flask_app.get("/health") def health(): return {"ok": True} def run_http(): flask_app.run(host="0.0.0.0", port=PORT, use_reloader=False) # ====== УТИЛИТЫ ====== async def fetch_text(url): timeout = aiohttp.ClientTimeout(total=15) async with aiohttp.ClientSession(timeout=timeout) as s: async with s.get(url) as r: r.raise_for_status() return await r.text() async def send_file(update: Update, text: str, filename: str): bio = io.BytesIO(text.encode("utf-8")) bio.name = filename await update.effective_message.reply_document(bio, filename=filename) # ====== МЕНЮ ====== MAIN_MENU = InlineKeyboardMarkup([ [InlineKeyboardButton("🎲 Рандом", callback_data="random"), InlineKeyboardButton("📚 Подписка", callback_data="subs")], [InlineKeyboardButton("🧩 Протокол", callback_data="proto")], [InlineKeyboardButton("🔑 Мой ключ", callback_data="key")] ]) # ====== ХЭНДЛЕРЫ ====== async def start(update: Update, ctx: ContextTypes.DEFAULT_TYPE): ensure_user(update.effective_user) await update.message.reply_text("Привет! Я моти впн.", reply_markup=MAIN_MENU) async def random_sub(update: Update, ctx): idx = random.randint(0, len(SUBSCRIPTION_URLS)-1) try: text = await fetch_text(SUBSCRIPTION_URLS[idx]) await send_file(update, text, f"sub{idx+1}.txt") except Exception as e: await update.callback_query.message.reply_text(f"Ошибка: {e}") async def show_subs(update: Update, ctx): kb = [[InlineKeyboardButton(f"Sub {i+1}", callback_data=f"sub:{i}")] for i in range(len(SUBSCRIPTION_URLS))] await update.callback_query.message.edit_text("Выбери:", reply_markup=InlineKeyboardMarkup(kb)) async def on_sub(update: Update, ctx): idx = int(update.callback_query.data.split(":")[1]) text = await fetch_text(SUBSCRIPTION_URLS[idx]) await send_file(update, text, f"sub{idx+1}.txt") async def show_proto(update: Update, ctx): kb = [[InlineKeyboardButton(k, callback_data=f"proto:{k}")] for k in PROTOCOL_URLS] await update.callback_query.message.edit_text("Протокол:", reply_markup=InlineKeyboardMarkup(kb)) async def on_proto(update: Update, ctx): key = update.callback_query.data.split(":")[1] text = await fetch_text(PROTOCOL_URLS[key]) await send_file(update, text, f"{key}.txt") async def my_key(update: Update, ctx): k = get_key(update.effective_user.id) if k: await update.callback_query.message.reply_text(f"Твой ключ:\n{k}", parse_mode="HTML") else: await update.callback_query.message.reply_text("Нет ключа. Пришли его сюда.") ctx.user_data["await_key"] = True async def on_text(update: Update, ctx): if ctx.user_data.get("await_key"): val = update.message.text.strip() set_key(update.effective_user.id, val) await update.message.reply_text("Сохранил ✅", reply_markup=MAIN_MENU) ctx.user_data.pop("await_key") # ====== MAIN ====== if __name__ == "__main__": init_db() Thread(target=run_http, daemon=True).start() app = Application.builder().token(BOT_TOKEN).build() app.add_handler(CommandHandler("start", start)) app.add_handler(CallbackQueryHandler(random_sub, pattern="^random$")) app.add_handler(CallbackQueryHandler(show_subs, pattern="^subs$")) app.add_handler(CallbackQueryHandler(on_sub, pattern="^sub:\\d+$")) app.add_handler(CallbackQueryHandler(show_proto, pattern="^proto$")) app.add_handler(CallbackQueryHandler(on_proto, pattern="^proto:")) app.add_handler(CallbackQueryHandler(my_key, pattern="^key$")) app.add_handler(MessageHandler(filters.TEXT & ~filters.COMMAND, on_text)) log.info("🚀 Бот запущен...") app.run_polling()