bafifi4972 commited on
Commit
5df13c5
·
verified ·
1 Parent(s): 76fcad8

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +181 -0
app.py ADDED
@@ -0,0 +1,181 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import io
3
+ import random
4
+ import logging
5
+ import sqlite3
6
+ import asyncio
7
+ from datetime import datetime
8
+ from threading import Thread
9
+
10
+ import aiohttp
11
+ from flask import Flask
12
+ from telegram import Update, InlineKeyboardButton, InlineKeyboardMarkup
13
+ from telegram.ext import (
14
+ Application, CommandHandler, CallbackQueryHandler, MessageHandler,
15
+ ContextTypes, filters
16
+ )
17
+
18
+ # ====== ЛОГИ ======
19
+ logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s")
20
+ log = logging.getLogger("moti-vpn")
21
+
22
+ # ====== НАСТРОЙКИ ======
23
+ BOT_TOKEN = os.environ.get("botvpntoken")
24
+ if not BOT_TOKEN:
25
+ raise SystemExit("❌ Не найден ENV botvpntoken")
26
+
27
+ DB_PATH = "/data/bot.db"
28
+ PORT = int(os.environ.get("PORT", "7860"))
29
+
30
+ SUBSCRIPTION_URLS = [
31
+ "https://raw.githubusercontent.com/barry-far/V2ray-config/main/Sub1.txt",
32
+ "https://raw.githubusercontent.com/barry-far/V2ray-config/main/Sub2.txt",
33
+ "https://raw.githubusercontent.com/barry-far/V2ray-config/main/Sub3.txt",
34
+ "https://raw.githubusercontent.com/barry-far/V2ray-config/main/Sub4.txt",
35
+ "https://raw.githubusercontent.com/barry-far/V2ray-config/main/Sub5.txt",
36
+ "https://raw.githubusercontent.com/barry-far/V2ray-config/main/Sub6.txt",
37
+ "https://raw.githubusercontent.com/barry-far/V2ray-config/main/Sub7.txt",
38
+ "https://raw.githubusercontent.com/barry-far/V2ray-config/main/Sub8.txt",
39
+ ]
40
+
41
+ PROTOCOL_URLS = {
42
+ "vmess": "https://raw.githubusercontent.com/barry-far/V2ray-config/main/Splitted-By-Protocol/vmess.txt",
43
+ "vless": "https://raw.githubusercontent.com/barry-far/V2ray-config/main/Splitted-By-Protocol/vless.txt",
44
+ "trojan": "https://raw.githubusercontent.com/barry-far/V2ray-config/main/Splitted-By-Protocol/trojan.txt",
45
+ "ss": "https://raw.githubusercontent.com/barry-far/V2ray-config/main/Splitted-By-Protocol/ss.txt",
46
+ "ssr": "https://raw.githubusercontent.com/barry-far/V2ray-config/main/Splitted-By-Protocol/ssr.txt",
47
+ }
48
+
49
+ # ====== SQLITE ======
50
+ os.makedirs("/data", exist_ok=True)
51
+ def db():
52
+ conn = sqlite3.connect(DB_PATH)
53
+ conn.row_factory = sqlite3.Row
54
+ return conn
55
+
56
+ def init_db():
57
+ with db() as c:
58
+ c.execute("""CREATE TABLE IF NOT EXISTS users (
59
+ tg_id INTEGER PRIMARY KEY,
60
+ username TEXT, first_name TEXT, created_at TEXT, custom_key TEXT
61
+ )""")
62
+ c.execute("""CREATE TABLE IF NOT EXISTS events (
63
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
64
+ tg_id INTEGER, type TEXT, payload TEXT, created_at TEXT
65
+ )""")
66
+ c.commit()
67
+
68
+ def ensure_user(u):
69
+ with db() as c:
70
+ cur = c.execute("SELECT tg_id FROM users WHERE tg_id=?", (u.id,))
71
+ if not cur.fetchone():
72
+ c.execute("INSERT INTO users (tg_id, username, first_name, created_at) VALUES (?,?,?,?)",
73
+ (u.id, u.username, u.first_name, datetime.utcnow().isoformat()))
74
+ c.commit()
75
+
76
+ def set_key(tg_id, val):
77
+ with db() as c:
78
+ c.execute("UPDATE users SET custom_key=? WHERE tg_id=?", (val, tg_id))
79
+ c.commit()
80
+
81
+ def get_key(tg_id):
82
+ with db() as c:
83
+ cur = c.execute("SELECT custom_key FROM users WHERE tg_id=?", (tg_id,))
84
+ row = cur.fetchone()
85
+ return row[0] if row else None
86
+
87
+ # ====== HTTP health ======
88
+ flask_app = Flask(__name__)
89
+ @flask_app.get("/")
90
+ @flask_app.get("/health")
91
+ def health():
92
+ return {"ok": True}
93
+
94
+ def run_http():
95
+ flask_app.run(host="0.0.0.0", port=PORT, use_reloader=False)
96
+
97
+ # ====== УТИЛИТЫ ======
98
+ async def fetch_text(url):
99
+ timeout = aiohttp.ClientTimeout(total=15)
100
+ async with aiohttp.ClientSession(timeout=timeout) as s:
101
+ async with s.get(url) as r:
102
+ r.raise_for_status()
103
+ return await r.text()
104
+
105
+ async def send_file(update: Update, text: str, filename: str):
106
+ bio = io.BytesIO(text.encode("utf-8"))
107
+ bio.name = filename
108
+ await update.effective_message.reply_document(bio, filename=filename)
109
+
110
+ # ====== МЕНЮ ======
111
+ MAIN_MENU = InlineKeyboardMarkup([
112
+ [InlineKeyboardButton("🎲 Рандом", callback_data="random"),
113
+ InlineKeyboardButton("📚 Подписка", callback_data="subs")],
114
+ [InlineKeyboardButton("🧩 Протокол", callback_data="proto")],
115
+ [InlineKeyboardButton("🔑 Мой ключ", callback_data="key")]
116
+ ])
117
+
118
+ # ====== ХЭНДЛЕРЫ ======
119
+ async def start(update: Update, ctx: ContextTypes.DEFAULT_TYPE):
120
+ ensure_user(update.effective_user)
121
+ await update.message.reply_text("Привет! Я моти впн.", reply_markup=MAIN_MENU)
122
+
123
+ async def random_sub(update: Update, ctx):
124
+ idx = random.randint(0, len(SUBSCRIPTION_URLS)-1)
125
+ try:
126
+ text = await fetch_text(SUBSCRIPTION_URLS[idx])
127
+ await send_file(update, text, f"sub{idx+1}.txt")
128
+ except Exception as e:
129
+ await update.callback_query.message.reply_text(f"Ошибка: {e}")
130
+
131
+ async def show_subs(update: Update, ctx):
132
+ kb = [[InlineKeyboardButton(f"Sub {i+1}", callback_data=f"sub:{i}")]
133
+ for i in range(len(SUBSCRIPTION_URLS))]
134
+ await update.callback_query.message.edit_text("Выбери:", reply_markup=InlineKeyboardMarkup(kb))
135
+
136
+ async def on_sub(update: Update, ctx):
137
+ idx = int(update.callback_query.data.split(":")[1])
138
+ text = await fetch_text(SUBSCRIPTION_URLS[idx])
139
+ await send_file(update, text, f"sub{idx+1}.txt")
140
+
141
+ async def show_proto(update: Update, ctx):
142
+ kb = [[InlineKeyboardButton(k, callback_data=f"proto:{k}")] for k in PROTOCOL_URLS]
143
+ await update.callback_query.message.edit_text("Протокол:", reply_markup=InlineKeyboardMarkup(kb))
144
+
145
+ async def on_proto(update: Update, ctx):
146
+ key = update.callback_query.data.split(":")[1]
147
+ text = await fetch_text(PROTOCOL_URLS[key])
148
+ await send_file(update, text, f"{key}.txt")
149
+
150
+ async def my_key(update: Update, ctx):
151
+ k = get_key(update.effective_user.id)
152
+ if k:
153
+ await update.callback_query.message.reply_text(f"Твой ключ:\n<code>{k}</code>", parse_mode="HTML")
154
+ else:
155
+ await update.callback_query.message.reply_text("Нет ключа. Пришли его сюда.")
156
+ ctx.user_data["await_key"] = True
157
+
158
+ async def on_text(update: Update, ctx):
159
+ if ctx.user_data.get("await_key"):
160
+ val = update.message.text.strip()
161
+ set_key(update.effective_user.id, val)
162
+ await update.message.reply_text("Сохранил ✅", reply_markup=MAIN_MENU)
163
+ ctx.user_data.pop("await_key")
164
+
165
+ # ====== MAIN ======
166
+ async def main():
167
+ init_db()
168
+ Thread(target=run_http, daemon=True).start()
169
+ app = Application.builder().token(BOT_TOKEN).build()
170
+ app.add_handler(CommandHandler("start", start))
171
+ app.add_handler(CallbackQueryHandler(random_sub, pattern="^random$"))
172
+ app.add_handler(CallbackQueryHandler(show_subs, pattern="^subs$"))
173
+ app.add_handler(CallbackQueryHandler(on_sub, pattern="^sub:\\d+$"))
174
+ app.add_handler(CallbackQueryHandler(show_proto, pattern="^proto$"))
175
+ app.add_handler(CallbackQueryHandler(on_proto, pattern="^proto:"))
176
+ app.add_handler(CallbackQueryHandler(my_key, pattern="^key$"))
177
+ app.add_handler(MessageHandler(filters.TEXT & ~filters.COMMAND, on_text))
178
+ await app.run_polling()
179
+
180
+ if __name__ == "__main__":
181
+ asyncio.run(main())