Spaces:
Runtime error
Runtime error
| 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__) | |
| 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<code>{k}</code>", 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() | |