1import io
2import math
3import re
4import random
5import string
6import os
7from datetime import datetime
8
9import qrcode
10import sqlalchemy
11from dateutil.relativedelta import relativedelta
12from telebot import types
13from telebot.util import user_link
14from telebot.asyncio_handler_backends import State, StatesGroup
15
16from app.db import GetDB, crud
17from app.models.user import (
18 UserCreate,
19 UserModify,
20 UserResponse,
21 UserStatus,
22 UserStatusModify,
23)
24from app.models.proxy import ProxyTypes
25from app.telegram import bot
26from app.telegram.utils.custom_filters import query_equals, query_startswith
27from app.telegram.utils.keyboard import BotKeyboard
28from app.utils.store import MemoryStorage
29from app.utils.system import cpu_usage, memory_usage, readable_size
30
31try:
32 from app.utils.system import realtime_bandwith as realtime_bandwidth
33except ImportError:
34 from app.utils.system import realtime_bandwidth
35
36from config import TELEGRAM_LOGGER_CHANNEL_ID, TELEGRAM_DEFAULT_VLESS_FLOW
37
38mem_store = MemoryStorage()
39
40
41class botStates(StatesGroup):
42 start = State()
43 add_data = State() # statesgroup should contain states
44 add_time = State()
45 edit_user_data_limit = State()
46 edit_user_expire = State()
47 edit_note = State()
48 # add_user_from_template = State()
49 add_user_username = State()
50 add_user_data_limit = State()
51 add_user_expire = State()
52
53
54def get_system_info():
55 mem = memory_usage()
56 cpu = cpu_usage()
57 with GetDB() as db:
58 bandwidth = crud.get_system_usage(db)
59 total_users = crud.get_users_count(db)
60 users_active = crud.get_users_count(db, UserStatus.active)
61 return """\
62🎛 *CPU Cores*: `{cpu_cores}`
63🖥 *CPU Usage*: `{cpu_percent}%`
64➖➖➖➖➖➖➖
65📊 *Total Memory*: `{total_memory}`
66📈 *In Use Memory*: `{used_memory}`
67📉 *Free Memory*: `{free_memory}`
68➖➖➖➖➖➖➖
69⬇️ *Download Usage*: `{down_bandwidth}`
70⬆️ *Upload Usage*: `{up_bandwidth}`
71↕️ *Total Usage*: `{total_bandwidth}`
72➖➖➖➖➖➖➖
73👥 *Total Users*: `{total_users}`
74🟢 *Active Users*: `{active_users}`
75🔴 *Deactivate Users*: `{deactivate_users}`
76➖➖➖➖➖➖➖
77⏫ *Upload Speed*: `{up_speed}`
78⏬ *Download Speed*: `{down_speed}`
79""".format(
80 cpu_cores=cpu.cores,
81 cpu_percent=cpu.percent,
82 total_memory=readable_size(mem.total),
83 used_memory=readable_size(mem.used),
84 free_memory=readable_size(mem.free),
85 total_bandwidth=readable_size(bandwidth.uplink + bandwidth.downlink),
86 up_bandwidth=readable_size(bandwidth.uplink),
87 down_bandwidth=readable_size(bandwidth.downlink),
88 total_users=total_users,
89 active_users=users_active,
90 deactivate_users=total_users - users_active,
91 up_speed=readable_size(realtime_bandwidth().outgoing_bytes),
92 down_speed=readable_size(realtime_bandwidth().incoming_bytes),
93 )
94
95
96def schedule_delete_message(chat_id, *message_ids: int) -> None:
97 messages: list[int] = mem_store.get(f"{chat_id}:messages_to_delete", [])
98 for mid in message_ids:
99 messages.append(mid)
100 mem_store.set(f"{chat_id}:messages_to_delete", messages)
101
102
103async def cleanup_messages(chat_id: int) -> None:
104 messages: list[int] = mem_store.get(f"{chat_id}:messages_to_delete", [])
105 for message_id in messages:
106 try:
107 await bot.delete_message(chat_id, message_id)
108 except:
109 pass
110 mem_store.set(f"{chat_id}:messages_to_delete", [])
111
112
113@bot.message_handler(commands=["start", "help"], is_admin=True)
114async def help_command(message: types.Message):
115 await cleanup_messages(message.chat.id)
116 # bot.clear_step_handler_by_chat_id(message.chat.id)
117 await bot.delete_state(message.from_user.id, message.chat.id)
118 return await bot.reply_to(
119 message,
120 """
121{user_link} Welcome to Marzban Telegram-Bot Admin Panel.
122Here you can manage your users and proxies.
123To get started, use the buttons below.
124""".format(
125 user_link=user_link(message.from_user)
126 ),
127 parse_mode="html",
128 reply_markup=BotKeyboard.main_menu(),
129 )
130
131
132@bot.callback_query_handler(query_equals("system"), is_admin=True)
133async def system_command(call: types.CallbackQuery):
134 return await bot.edit_message_text(
135 get_system_info(),
136 call.message.chat.id,
137 call.message.message_id,
138 parse_mode="MarkdownV2",
139 reply_markup=BotKeyboard.main_menu(),
140 )
141
142
143@bot.callback_query_handler(query_equals("restart"), is_admin=True)
144async def restart_command(call: types.CallbackQuery):
145 await bot.edit_message_text(
146 "⚠️ Are you sure? This will restart Xray core.",
147 call.message.chat.id,
148 call.message.message_id,
149 reply_markup=BotKeyboard.confirm_action(action="do_restart"),
150 )
151
152
153@bot.callback_query_handler(query_startswith("delete:"), is_admin=True)
154async def delete_user_command(call: types.CallbackQuery):
155 username = call.data.split(":")[1]
156 await bot.edit_message_text(
157 f"⚠️ Are you sure? This will delete user `{username}`.",
158 call.message.chat.id,
159 call.message.message_id,
160 parse_mode="markdown",
161 reply_markup=BotKeyboard.confirm_action(action="do_delete", username=username),
162 )
163
164
165@bot.callback_query_handler(query_startswith("suspend:"), is_admin=True)
166async def suspend_user_command(call: types.CallbackQuery):
167 username = call.data.split(":")[1]
168 await bot.edit_message_text(
169 f"⚠️ Are you sure? This will suspend user `{username}`.",
170 call.message.chat.id,
171 call.message.message_id,
172 parse_mode="markdown",
173 reply_markup=BotKeyboard.confirm_action(action="do_suspend", username=username),
174 )
175
176
177@bot.callback_query_handler(query_startswith("activate:"), is_admin=True)
178async def activate_user_command(call: types.CallbackQuery):
179 username = call.data.split(":")[1]
180 await bot.edit_message_text(
181 f"⚠️ Are you sure? This will activate user `{username}`.",
182 call.message.chat.id,
183 call.message.message_id,
184 parse_mode="markdown",
185 reply_markup=BotKeyboard.confirm_action(
186 action="do_activate", username=username
187 ),
188 )
189
190
191@bot.callback_query_handler(query_startswith("reset_usage:"), is_admin=True)
192async def reset_usage_user_command(call: types.CallbackQuery):
193 username = call.data.split(":")[1]
194 await bot.edit_message_text(
195 f"⚠️ Are you sure? This will Reset Usage of user `{username}`.",
196 call.message.chat.id,
197 call.message.message_id,
198 parse_mode="markdown",
199 reply_markup=BotKeyboard.confirm_action(
200 action="do_reset_usage", username=username
201 ),
202 )
203
204
205@bot.callback_query_handler(query_equals("edit_all"), is_admin=True)
206async def edit_all_command(call: types.CallbackQuery):
207 with GetDB() as db:
208 total_users = crud.get_users_count(db)
209 active_users = crud.get_users_count(db, UserStatus.active)
210 disabled_users = crud.get_users_count(db, UserStatus.disabled)
211 expired_users = crud.get_users_count(db, UserStatus.expired)
212 limited_users = crud.get_users_count(db, UserStatus.limited)
213 text = f"""
214👥 *Total Users*: `{total_users}`
215✅ *Active Users*: `{active_users}`
216❌ *Disabled Users*: `{disabled_users}`
217🕰 *Expired Users*: `{expired_users}`
218🪫 *Limited Users*: `{limited_users}`"""
219 return await bot.edit_message_text(
220 text,
221 call.message.chat.id,
222 call.message.message_id,
223 parse_mode="markdown",
224 reply_markup=BotKeyboard.edit_all_menu(),
225 )
226
227
228@bot.callback_query_handler(query_equals("delete_expired"), is_admin=True)
229async def delete_expired_command(call: types.CallbackQuery):
230 await bot.edit_message_text(
231 f"⚠️ Are you sure? This will *DELETE All Expired Users*‼️",
232 call.message.chat.id,
233 call.message.message_id,
234 parse_mode="markdown",
235 reply_markup=BotKeyboard.confirm_action(action="do_delete_expired"),
236 )
237
238
239@bot.callback_query_handler(query_equals("delete_limited"), is_admin=True)
240async def delete_limited_command(call: types.CallbackQuery):
241 await bot.edit_message_text(
242 f"⚠️ Are you sure? This will *DELETE All Limited Users*‼️",
243 call.message.chat.id,
244 call.message.message_id,
245 parse_mode="markdown",
246 reply_markup=BotKeyboard.confirm_action(action="do_delete_limited"),
247 )
248
249
250@bot.callback_query_handler(query_equals("add_data"), is_admin=True)
251async def add_data_command(call: types.CallbackQuery):
252 msg = await bot.edit_message_text(
253 f"🔋 Enter Data Limit to increase or decrease (GB):",
254 call.message.chat.id,
255 call.message.message_id,
256 reply_markup=BotKeyboard.inline_cancel_action(),
257 )
258 schedule_delete_message(call.message.chat.id, call.message.id)
259 schedule_delete_message(call.message.chat.id, msg.id)
260 # return bot.register_next_step_handler(call.message, add_data_step)
261 await bot.set_state(call.from_user.id, botStates.add_data, call.message.chat.id)
262
263
264@bot.message_handler(state=botStates.add_data)
265async def add_data_step(message):
266 try:
267 data_limit = float(message.text)
268 if not data_limit:
269 raise ValueError
270 except ValueError:
271 wait_msg = await bot.send_message(
272 message.chat.id, "❌ Data limit must be a number and not zero."
273 )
274 schedule_delete_message(message.chat.id, wait_msg.message_id)
275 return # bot.register_next_step_handler(wait_msg, add_data_step)
276 schedule_delete_message(message.chat.id, message.message_id)
277 msg = await bot.send_message(
278 message.chat.id,
279 f"⚠️ Are you sure? this will change Data limit of all users according to <b>"
280 f"{'+' if data_limit > 0 else '-'}{readable_size(abs(data_limit *1024*1024*1024))}</b>",
281 parse_mode="html",
282 reply_markup=BotKeyboard.confirm_action("do_add_data", data_limit),
283 )
284 await bot.delete_state(message.from_user.id, message.chat.id)
285 await cleanup_messages(message.chat.id)
286 schedule_delete_message(message.chat.id, msg.id)
287
288
289@bot.callback_query_handler(query_equals("add_time"), is_admin=True)
290async def add_time_command(call: types.CallbackQuery):
291 msg = await bot.edit_message_text(
292 f"📅 Enter Days to increase or decrease expiry:",
293 call.message.chat.id,
294 call.message.message_id,
295 reply_markup=BotKeyboard.inline_cancel_action(),
296 )
297 schedule_delete_message(call.message.chat.id, call.message.id)
298 schedule_delete_message(call.message.chat.id, msg.id)
299 # return bot.register_next_step_handler(call.message, add_time_step)
300 await bot.set_state(call.from_user.id, botStates.add_time, call.message.chat.id)
301
302
303@bot.message_handler(state=botStates.add_time)
304async def add_time_step(message):
305 try:
306 days = int(message.text)
307 if not days:
308 raise ValueError
309 except ValueError:
310 wait_msg = await bot.send_message(
311 message.chat.id, "❌ Days must be as a number and not zero."
312 )
313 schedule_delete_message(message.chat.id, wait_msg.message_id)
314 return # bot.register_next_step_handler(wait_msg, add_time_step)
315 schedule_delete_message(message.chat.id, message.message_id)
316 msg = await bot.send_message(
317 message.chat.id,
318 f"⚠️ Are you sure? this will change Expiry Time of all users according to <b>{days} Days</b>",
319 parse_mode="html",
320 reply_markup=BotKeyboard.confirm_action("do_add_time", days),
321 )
322 await cleanup_messages(message.chat.id)
323 schedule_delete_message(message.chat.id, msg.id)
324
325
326@bot.callback_query_handler(query_startswith("inbound"), is_admin=True)
327async def inbound_command(call: types.CallbackQuery):
328 await bot.edit_message_text(
329 f"Select inbound to *{call.data[8:].title()}* from all users",
330 call.message.chat.id,
331 call.message.message_id,
332 parse_mode="markdown",
333 reply_markup=BotKeyboard.inbounds_menu(call.data, xray.config.inbounds_by_tag),
334 )
335
336
337@bot.callback_query_handler(query_startswith("confirm_inbound"), is_admin=True)
338async def delete_expired_confirm_command(call: types.CallbackQuery):
339 await bot.edit_message_text(
340 f"⚠️ Are you sure? This will *{call.data[16:].replace(':', ' ')} for All Users*‼️",
341 call.message.chat.id,
342 call.message.message_id,
343 parse_mode="markdown",
344 reply_markup=BotKeyboard.confirm_action(action=call.data[8:]),
345 )
346
347
348@bot.callback_query_handler(query_startswith("edit:"), is_admin=True)
349async def edit_command(call: types.CallbackQuery):
350 # bot.clear_step_handler_by_chat_id(call.message.chat.id)
351 await bot.delete_state(call.from_user.id, call.message.chat.id)
352 username = call.data.split(":")[1]
353 with GetDB() as db:
354 db_user = crud.get_user(db, username)
355 if not db_user:
356 return await bot.answer_callback_query(
357 call.id, "❌ User not found.", show_alert=True
358 )
359 services = crud.get_services(db)
360 user = UserResponse.model_validate(db_user)
361 mem_store.set(f"{call.message.chat.id}:username", username)
362 mem_store.set(f"{call.message.chat.id}:data_limit", db_user.data_limit)
363 mem_store.set(
364 f"{call.message.chat.id}:expire_date",
365 datetime.fromtimestamp(db_user.expire) if db_user.expire else None,
366 )
367 mem_store.set(f"{call.message.chat.id}:services", [s.id for s in db_user.services])
368 await bot.edit_message_text(
369 f"📝 Editing user `{username}`",
370 call.message.chat.id,
371 call.message.message_id,
372 parse_mode="markdown",
373 reply_markup=BotKeyboard.select_services(
374 services,
375 [s.id for s in user.services],
376 "edit",
377 username=username,
378 data_limit=db_user.data_limit,
379 expire_date=mem_store.get(f"{call.message.chat.id}:expire_date"),
380 ),
381 )
382
383
384@bot.callback_query_handler(query_equals("help_edit"), is_admin=True)
385async def help_edit_command(call: types.CallbackQuery):
386 await bot.answer_callback_query(
387 call.id, text="Press the (✏️ Edit) button to edit", show_alert=True
388 )
389
390
391@bot.callback_query_handler(query_equals("cancel"), is_admin=True)
392async def cancel_command(call: types.CallbackQuery):
393 # bot.clear_step_handler_by_chat_id(call.message.chat.id)
394 await bot.delete_state(call.from_user.id, call.message.chat.id)
395 return await bot.edit_message_text(
396 get_system_info(),
397 call.message.chat.id,
398 call.message.message_id,
399 parse_mode="MarkdownV2",
400 reply_markup=BotKeyboard.main_menu(),
401 )
402
403
404@bot.callback_query_handler(query_startswith("edit_user:"), is_admin=True)
405async def edit_user_command(call: types.CallbackQuery):
406 _, username, action = call.data.split(":")
407 schedule_delete_message(call.message.chat.id, call.message.id)
408 await cleanup_messages(call.message.chat.id)
409 if action == "data":
410 msg = await bot.send_message(
411 call.message.chat.id,
412 "⬆️ Enter Data Limit (GB):\n⚠️ Send 0 for unlimited.",
413 reply_markup=BotKeyboard.inline_cancel_action(f"user:{username}"),
414 )
415 mem_store.set(f"{call.message.chat.id}:edit_msg_text", call.message.text)
416 await bot.delete_state(call.from_user.id, call.message.chat.id)
417 await bot.set_state(
418 call.from_user.id, botStates.edit_user_data_limit, call.message.chat.id
419 )
420 async with bot.retrieve_data(call.from_user.id, call.message.chat.id) as data:
421 data["username"] = username
422 # bot.clear_step_handler_by_chat_id(call.message.chat.id)
423 # bot.register_next_step_handler(
424 # call.message, edit_user_data_limit_step, username)
425 schedule_delete_message(call.message.chat.id, msg.message_id)
426 elif action == "expire":
427 msg = await bot.send_message(
428 call.message.chat.id,
429 "⬆️ Enter Expire Date (YYYY-MM-DD)\nOr You Can Use Regex Symbol: ^[0-9]{1,3}(M|D) :\n⚠️ Send 0 for never expire.",
430 reply_markup=BotKeyboard.inline_cancel_action(f"user:{username}"),
431 )
432 mem_store.set(f"{call.message.chat.id}:edit_msg_text", call.message.text)
433 # bot.clear_step_handler_by_chat_id(call.message.chat.id)
434 await bot.set_state(
435 call.from_user.id, botStates.edit_user_expire, call.message.chat.id
436 )
437 async with bot.retrieve_data(call.from_user.id, call.message.chat.id) as data:
438 data["username"] = username
439 # bot.register_next_step_handler(
440 # call.message, edit_user_expire_step, username=username)
441 schedule_delete_message(call.message.chat.id, msg.message_id)
442
443
444@bot.message_handler(state=botStates.edit_user_data_limit)
445async def edit_user_data_limit_step(message: types.Message):
446 try:
447 if float(message.text) < 0:
448 wait_msg = await bot.send_message(
449 message.chat.id, "❌ Data limit must be greater or equal to 0."
450 )
451 schedule_delete_message(message.chat.id, wait_msg.message_id)
452 return # bot.register_next_step_handler(wait_msg, edit_user_data_limit_step, username=username)
453 data_limit = float(message.text) * 1024**3
454 except ValueError:
455 wait_msg = await bot.send_message(
456 message.chat.id, "❌ Data limit must be a number."
457 )
458 schedule_delete_message(message.chat.id, wait_msg.message_id)
459 return # bot.register_next_step_handler(wait_msg, edit_user_data_limit_step, username=username)
460 mem_store.set(f"{message.chat.id}:data_limit", data_limit)
461 schedule_delete_message(message.chat.id, message.message_id)
462 text = mem_store.get(f"{message.chat.id}:edit_msg_text")
463 mem_store.delete(f"{message.chat.id}:edit_msg_text")
464 async with bot.retrieve_data(call.from_user.id, call.message.chat.id) as data:
465 username = data["username"]
466 del data["username"]
467 await bot.send_message(
468 message.chat.id,
469 text or f"📝 Editing user <code>{username}</code>",
470 parse_mode="html",
471 reply_markup=BotKeyboard.select_protocols(
472 mem_store.get(f"{message.chat.id}:protocols"),
473 "edit",
474 username=username,
475 data_limit=data_limit,
476 expire_date=mem_store.get(f"{message.chat.id}:expire_date"),
477 ),
478 )
479 await cleanup_messages(message.chat.id)
480
481
482@bot.message_handler(state=botStates.edit_user_expire)
483async def edit_user_expire_step(message: types.Message):
484 try:
485 now = datetime.now()
486 today = datetime(
487 year=now.year, month=now.month, day=now.day, hour=23, minute=59, second=59
488 )
489 if re.match(r"^[0-9]{1,3}(M|m|D|d)$", message.text):
490 expire_date = today
491 number_pattern = r"^[0-9]{1,3}"
492 number = int(re.findall(number_pattern, message.text)[0])
493 symbol_pattern = r"(M|m|D|d)$"
494 symbol = re.findall(symbol_pattern, message.text)[0].upper()
495 if symbol == "M":
496 expire_date = today + relativedelta(months=number)
497 elif symbol == "D":
498 expire_date = today + relativedelta(days=number)
499 elif message.text != "0":
500 expire_date = datetime.strptime(message.text, "%Y-%m-%d")
501 else:
502 expire_date = None
503 if expire_date and expire_date < today:
504 wait_msg = await bot.send_message(
505 message.chat.id, "❌ Expire date must be greater than today."
506 )
507 schedule_delete_message(message.chat.id, wait_msg.message_id)
508 return # bot.register_next_step_handler(wait_msg, edit_user_expire_step, username=username)
509 except ValueError:
510 wait_msg = await bot.send_message(
511 message.chat.id,
512 "❌ Expire date must be in YYYY-MM-DD format.\nOr You Can Use Regex Symbol: ^[0-9]{1,3}(M|D)",
513 )
514 schedule_delete_message(message.chat.id, wait_msg.message_id)
515 return # bot.register_next_step_handler(wait_msg, edit_user_expire_step, username=username)
516
517 mem_store.set(f"{message.chat.id}:expire_date", expire_date)
518 schedule_delete_message(message.chat.id, message.message_id)
519 text = mem_store.get(f"{message.chat.id}:edit_msg_text")
520 mem_store.delete(f"{message.chat.id}:edit_msg_text")
521 async with bot.retrieve_data(call.from_user.id, call.message.chat.id) as data:
522 username = data["username"]
523 del data["username"]
524 await bot.send_message(
525 message.chat.id,
526 text or f"📝 Editing user <code>{username}</code>",
527 parse_mode="html",
528 reply_markup=BotKeyboard.select_protocols(
529 mem_store.get(f"{message.chat.id}:protocols"),
530 "edit",
531 username=username,
532 data_limit=mem_store.get(f"{message.chat.id}:data_limit"),
533 expire_date=expire_date,
534 ),
535 )
536 await cleanup_messages(message.chat.id)
537
538
539@bot.callback_query_handler(query_startswith("users:"), is_admin=True)
540async def users_command(call: types.CallbackQuery):
541 page = int(call.data.split(":")[1]) if len(call.data.split(":")) > 1 else 1
542 with GetDB() as db:
543 total_pages = math.ceil(crud.get_users_count(db) / 10)
544 users = crud.get_users(
545 db,
546 offset=(page - 1) * 10,
547 limit=10,
548 sort=[crud.UsersSortingOptions["-created_at"]],
549 )
550 text = """👥 Users: (Page {page}/{total_pages})
551✅ Active
552❌ Disabled
553🕰 Expired
554🪫 Limited""".format(
555 page=page, total_pages=total_pages
556 )
557
558 await bot.edit_message_text(
559 text,
560 call.message.chat.id,
561 call.message.message_id,
562 parse_mode="HTML",
563 reply_markup=BotKeyboard.user_list(users, page, total_pages=total_pages),
564 )
565
566
567def get_user_info_text(
568 status: str,
569 username: str,
570 sub_url: str,
571 data_limit: int = None,
572 usage: int = None,
573 expire: int = None,
574 note: str = None,
575) -> str:
576 statuses = {"active": "✅", "expired": "🕰", "limited": "🪫", "disabled": "❌"}
577 text = f"""\
578┌─{statuses[status]} <b>Status:</b> <code>{status.title()}</code>
579│ └─<b>Username:</b> <code>{username}</code>
580│
581├─🔋 <b>Data limit:</b> <code>{readable_size(data_limit) if data_limit else 'Unlimited'}</code>
582│ └─<b>Data Used:</b> <code>{readable_size(usage) if usage else "-"}</code>
583│
584├─📅 <b>Expiry Date:</b> <code>{datetime.fromtimestamp(expire).date() if expire else 'Never'}</code>
585│ └─<b>Days left:</b> <code>{(datetime.fromtimestamp(expire or 0) - datetime.now()).days if expire else '-'}</code>
586│
587"""
588 if note:
589 text += f"├─📝 <b>Note:</b> <code>{note}</code>\n│\n"
590 text += f'└─🚀 <b><a href="{sub_url}">Subscription</a>:</b> <code>{sub_url}</code>'
591 return text
592
593
594@bot.callback_query_handler(query_startswith("edit_note:"), is_admin=True)
595async def edit_note_command(call: types.CallbackQuery):
596 username = call.data.split(":")[1]
597 with GetDB() as db:
598 db_user = crud.get_user(db, username)
599 if not db_user:
600 return await bot.answer_callback_query(
601 call.id, "❌ User not found.", show_alert=True
602 )
603 schedule_delete_message(call.message.chat.id, call.message.id)
604 await cleanup_messages(call.message.chat.id)
605 msg = await bot.send_message(
606 call.message.chat.id,
607 f"<b>📝 Current Note:</b> <code>{db_user.note}</code>\n\nSend new Note for <code>{username}</code>",
608 parse_mode="HTML",
609 reply_markup=BotKeyboard.inline_cancel_action(f"user:{username}"),
610 )
611 mem_store.set(f"{call.message.chat.id}:username", username)
612 schedule_delete_message(call.message.chat.id, msg.id)
613 await bot.set_state(call.from_user.id, botStates.edit_note, call.message.chat.id)
614 # bot.register_next_step_handler(msg, edit_note_step)
615
616
617@bot.message_handler(state=botStates.edit_note)
618async def edit_note_step(message: types.Message):
619 note = message.text or ""
620 if len(note) > 500:
621 wait_msg = await bot.send_message(
622 message.chat.id, "❌ Note can not be more than 500 characters."
623 )
624 schedule_delete_message(message.chat.id, wait_msg.id)
625 schedule_delete_message(message.chat.id, message.id)
626 return # bot.register_next_step_handler(wait_msg, edit_note_step)
627 username = mem_store.get(f"{message.chat.id}:username")
628 if not username:
629 await cleanup_messages(message.chat.id)
630 await bot.reply_to(message, "❌ Something went wrong!\n restart bot /start")
631 with GetDB() as db:
632 db_user = crud.get_user(db, username)
633 last_note = db_user.note
634 db_user = crud.update_user(
635 db, db_user, UserModify(username=username, note=note)
636 )
637 user = UserResponse.model_validate(db_user)
638 text = get_user_info_text(
639 status=user.status,
640 username=user.username,
641 sub_url=user.subscription_url,
642 expire=user.expire,
643 data_limit=user.data_limit,
644 usage=user.used_traffic,
645 note=user.note,
646 )
647 await bot.reply_to(
648 message,
649 text,
650 parse_mode="html",
651 reply_markup=BotKeyboard.user_menu(
652 user_info={"status": user.status, "username": user.username}, note=True
653 ),
654 )
655 if TELEGRAM_LOGGER_CHANNEL_ID:
656 text = f"""\
657📝 <b>#Edit_Note #From_Bot</b>
658➖➖➖➖➖➖➖➖➖
659<b>Username :</b> <code>{username}</code>
660<b>Last Note :</b> <code>{last_note}</code>
661<b>New Note :</b> <code>{note}</code>
662➖➖➖➖➖➖➖➖➖
663<b>By :</b> <a href="tg://user?id={message.chat.id}">{message.from_user.full_name}</a>"""
664 try:
665 await bot.send_message(TELEGRAM_LOGGER_CHANNEL_ID, text, "HTML")
666 except:
667 pass
668
669
670@bot.callback_query_handler(query_startswith("user:"), is_admin=True)
671async def user_command(call: types.CallbackQuery):
672 await bot.delete_state(call.from_user.id, call.message.chat.id)
673 # bot.clear_step_handler_by_chat_id(call.message.chat.id)
674 username = call.data.split(":")[1]
675 page = int(call.data.split(":")[2]) if len(call.data.split(":")) > 2 else 1
676 with GetDB() as db:
677 db_user = crud.get_user(db, username)
678 if not db_user:
679 return await bot.answer_callback_query(
680 call.id, "❌ User not found.", show_alert=True
681 )
682 user = UserResponse.model_validate(db_user)
683 text = get_user_info_text(
684 status=user.status,
685 username=username,
686 sub_url=user.subscription_url,
687 data_limit=user.data_limit,
688 usage=user.used_traffic,
689 expire=user.expire,
690 note=user.note,
691 )
692 await bot.edit_message_text(
693 text,
694 call.message.chat.id,
695 call.message.message_id,
696 parse_mode="HTML",
697 reply_markup=BotKeyboard.user_menu(
698 {"username": user.username, "status": user.status}, page=page, note=True
699 ),
700 )
701
702
703@bot.callback_query_handler(query_startswith("revoke_sub:"), is_admin=True)
704async def revoke_sub_command(call: types.CallbackQuery):
705 username = call.data.split(":")[1]
706 await bot.edit_message_text(
707 f"⚠️ Are you sure? This will *Revoke Subscription* link for `{username}`‼️",
708 call.message.chat.id,
709 call.message.message_id,
710 parse_mode="markdown",
711 reply_markup=BotKeyboard.confirm_action(action="do_revoke", username=username),
712 )
713
714
715@bot.callback_query_handler(query_startswith("links:"), is_admin=True)
716async def links_command(call: types.CallbackQuery):
717 username = call.data.split(":")[1]
718
719 with GetDB() as db:
720 db_user = crud.get_user(db, username)
721 if not db_user:
722 return await bot.answer_callback_query(
723 call.id, "User not found!", show_alert=True
724 )
725
726 user = UserResponse.model_validate(db_user)
727
728 text = f"<code>{user.subscription_url}</code>\n\n\n"
729 for link in user.links:
730 text += f"<code>{link}</code>\n\n"
731
732 await bot.edit_message_text(
733 text,
734 call.message.chat.id,
735 call.message.message_id,
736 parse_mode="HTML",
737 reply_markup=BotKeyboard.show_links(username),
738 )
739
740
741@bot.callback_query_handler(query_startswith("genqr:"), is_admin=True)
742async def genqr_command(call: types.CallbackQuery):
743 username = call.data.split(":")[1]
744
745 with GetDB() as db:
746 db_user = crud.get_user(db, username)
747 if not db_user:
748 return await bot.answer_callback_query(
749 call.id, "User not found!", show_alert=True
750 )
751
752 user = UserResponse.model_validate(db_user)
753
754 await bot.answer_callback_query(call.id, "Generating QR code...")
755
756 for link in user.links:
757 f = io.BytesIO()
758 qr = qrcode.QRCode(border=6)
759 qr.add_data(link)
760 qr.make_image().save(f)
761 f.seek(0)
762 await bot.send_photo(
763 call.message.chat.id,
764 photo=f,
765 caption=f"<code>{link}</code>",
766 parse_mode="HTML",
767 )
768 with io.BytesIO() as f:
769 qr = qrcode.QRCode(border=6)
770 qr.add_data(user.subscription_url)
771 qr.make_image().save(f)
772 f.seek(0)
773 await bot.send_photo(
774 call.message.chat.id,
775 photo=f,
776 caption=get_user_info_text(
777 status=user.status,
778 username=user.username,
779 sub_url=user.subscription_url,
780 data_limit=user.data_limit,
781 usage=user.used_traffic,
782 expire=user.expire,
783 ),
784 parse_mode="HTML",
785 reply_markup=BotKeyboard.subscription_page(user.subscription_url),
786 )
787 try:
788 await bot.delete_message(call.message.chat.id, call.message.message_id)
789 except:
790 pass
791
792 text = f"<code>{user.subscription_url}</code>\n\n\n"
793 for link in user.links:
794 text += f"<code>{link}</code>\n\n"
795
796 await bot.send_message(
797 call.message.chat.id,
798 text,
799 "HTML",
800 reply_markup=BotKeyboard.show_links(username),
801 )
802
803
804@bot.callback_query_handler(query_startswith("random"), is_admin=True)
805async def random_username(call: types.CallbackQuery):
806 # bot.clear_step_handler_by_chat_id(call.message.chat.id)
807 await bot.delete_state(call.from_user.id, call.message.chat.id)
808 template_id = int(call.data.split(":")[1] or 0)
809 mem_store.delete(f"{call.message.chat.id}:template_id")
810
811 characters = string.ascii_letters + string.digits
812 username = (
813 random.choice(characters)
814 + "".join(random.choices(characters, k=4))
815 + "_"
816 + "".join(random.choices(characters, k=4))
817 )
818
819 schedule_delete_message(call.message.chat.id, call.message.id)
820 await cleanup_messages(call.message.chat.id)
821
822 msg = await bot.send_message(
823 call.message.chat.id,
824 "⬆️ Enter Data Limit (GB):\n⚠️ Send 0 for unlimited.",
825 reply_markup=BotKeyboard.inline_cancel_action(),
826 )
827 schedule_delete_message(call.message.chat.id, msg.id)
828 await bot.set_state(
829 call.from_user.id, botStates.add_user_data_limit, call.message.chat.id
830 )
831 async with bot.retrieve_data(call.from_user.id, call.message.chat.id) as data:
832 data["username"] = username
833 return
834
835
836@bot.callback_query_handler(query_equals("add_user"), is_admin=True)
837async def add_user_command(call: types.CallbackQuery):
838 try:
839 await bot.delete_message(call.message.chat.id, call.message.message_id)
840 except: # noqa
841 pass
842 username_msg = await bot.send_message(
843 call.message.chat.id,
844 "👤 Enter username:\n⚠️Username only can be 3 to 32 characters and contain a-z, A-Z 0-9, and underscores in "
845 "between.",
846 reply_markup=BotKeyboard.random_username(),
847 )
848 schedule_delete_message(call.message.chat.id, username_msg.id)
849 await bot.set_state(
850 call.from_user.id, botStates.add_user_username, call.message.chat.id
851 )
852 # bot.register_next_step_handler(username_msg, add_user_username_step)
853
854
855@bot.message_handler(state=botStates.add_user_username)
856async def add_user_username_step(message: types.Message):
857 username = message.text
858 if not username:
859 wait_msg = await bot.send_message(
860 message.chat.id, "❌ Username can not be empty."
861 )
862 schedule_delete_message(message.chat.id, wait_msg.id)
863 schedule_delete_message(message.chat.id, message.id)
864 return # bot.register_next_step_handler(wait_msg, add_user_username_step)
865 if not re.match(r"^(?!.*__)(?!.*_$)\w{2,31}[a-zA-Z\d]$", username):
866 wait_msg = await bot.send_message(
867 message.chat.id,
868 "❌ Username only can be 3 to 32 characters and contain a-z, A-Z, 0-9, and underscores in between.",
869 )
870 schedule_delete_message(message.chat.id, wait_msg.id)
871 schedule_delete_message(message.chat.id, message.id)
872 return # bot.register_next_step_handler(wait_msg, add_user_username_step)
873 with GetDB() as db:
874 if crud.get_user(db, username):
875 wait_msg = await bot.send_message(
876 message.chat.id, "❌ Username already exists."
877 )
878 schedule_delete_message(message.chat.id, wait_msg.id)
879 schedule_delete_message(message.chat.id, message.id)
880 return # bot.register_next_step_handler(wait_msg, add_user_username_step)
881 schedule_delete_message(message.chat.id, message.id)
882 await cleanup_messages(message.chat.id)
883 msg = await bot.send_message(
884 message.chat.id,
885 "⬆️ Enter Data Limit (GB):\n⚠️ Send 0 for unlimited.",
886 reply_markup=BotKeyboard.inline_cancel_action(),
887 )
888 schedule_delete_message(message.chat.id, msg.id)
889 await bot.set_state(
890 message.from_user.id, botStates.add_user_data_limit, message.chat.id
891 )
892 async with bot.retrieve_data(message.from_user.id, message.chat.id) as data:
893 data["username"] = username
894 # bot.register_next_step_handler(msg, add_user_data_limit_step, username=username)
895
896
897@bot.message_handler(state=botStates.add_user_data_limit)
898async def add_user_data_limit_step(message: types.Message):
899 try:
900 if float(message.text) < 0:
901 wait_msg = await bot.send_message(
902 message.chat.id, "❌ Data limit must be greater or equal to 0."
903 )
904 schedule_delete_message(message.chat.id, wait_msg.id)
905 schedule_delete_message(message.chat.id, message.id)
906 return # bot.register_next_step_handler(wait_msg, add_user_data_limit_step, username=username)
907 data_limit = float(message.text) * 1024 * 1024 * 1024
908 except ValueError:
909 wait_msg = await bot.send_message(
910 message.chat.id, "❌ Data limit must be a number."
911 )
912 schedule_delete_message(message.chat.id, wait_msg.id)
913 schedule_delete_message(message.chat.id, message.id)
914 return # bot.register_next_step_handler(wait_msg, add_user_data_limit_step, username=username)
915 schedule_delete_message(message.chat.id, message.id)
916 await cleanup_messages(message.chat.id)
917 msg = await bot.send_message(
918 message.chat.id,
919 "⬆️ Enter Expire Date (YYYY-MM-DD)\nOr You Can Use Regex Symbol: ^[0-9]{1,3}(M|D) :\n⚠️ Send 0 for never expire.",
920 reply_markup=BotKeyboard.inline_cancel_action(),
921 )
922 schedule_delete_message(message.chat.id, msg.id)
923 await bot.set_state(
924 message.from_user.id, botStates.add_user_expire, message.chat.id
925 )
926 async with bot.retrieve_data(message.from_user.id, message.chat.id) as data:
927 data["data_limit"] = data_limit
928
929 # bot.register_next_step_handler(msg, add_user_expire_step, username=username, data_limit=data_limit)
930
931
932@bot.message_handler(state=botStates.add_user_expire)
933async def add_user_expire_step(message: types.Message):
934 try:
935 now = datetime.now()
936 today = datetime(
937 year=now.year, month=now.month, day=now.day, hour=23, minute=59, second=59
938 )
939 if re.match(r"^[0-9]{1,3}(M|m|D|d)$", message.text):
940 expire_date = today
941 number_pattern = r"^[0-9]{1,3}"
942 number = int(re.findall(number_pattern, message.text)[0])
943 symbol_pattern = r"(M|m|D|d)$"
944 symbol = re.findall(symbol_pattern, message.text)[0].upper()
945 if symbol == "M":
946 expire_date = today + relativedelta(months=number)
947 elif symbol == "D":
948 expire_date = today + relativedelta(days=number)
949 elif message.text != "0":
950 expire_date = datetime.strptime(message.text, "%Y-%m-%d")
951 else:
952 expire_date = None
953 if expire_date and expire_date < today:
954 wait_msg = await bot.send_message(
955 message.chat.id, "❌ Expire date must be greater than today."
956 )
957 schedule_delete_message(message.chat.id, wait_msg.id)
958 schedule_delete_message(message.chat.id, message.id)
959 return # bot.register_next_step_handler(wait_msg, add_user_expire_step, username=username, data_limit=data_limit)
960 except ValueError:
961 wait_msg = await bot.send_message(
962 message.chat.id,
963 "❌ Expire date must be in YYYY-MM-DD format.\nOr You Can Use Regex Symbol: ^[0-9]{1,3}(M|D)",
964 )
965 schedule_delete_message(message.chat.id, wait_msg.id)
966 schedule_delete_message(message.chat.id, message.id)
967 return # bot.register_next_step_handler(wait_msg, add_user_expire_step, username=username, data_limit=data_limit)
968 async with bot.retrieve_data(message.from_user.id, message.chat.id) as data:
969 username, data_limit = data["username"], data["data_limit"]
970 mem_store.set(f"{message.chat.id}:username", username)
971 mem_store.set(f"{message.chat.id}:data_limit", data_limit)
972 mem_store.set(f"{message.chat.id}:expire_date", expire_date)
973
974 with GetDB() as db:
975 services = crud.get_services(db)
976 schedule_delete_message(message.chat.id, message.id)
977 await cleanup_messages(message.chat.id)
978 await bot.send_message(
979 message.chat.id,
980 "Select Services:\nUsernames: {}\nData Limit: {}\nExpiry Date {}".format(
981 mem_store.get(f"{message.chat.id}:username"),
982 (
983 readable_size(mem_store.get(f"{message.chat.id}:data_limit"))
984 if mem_store.get(f"{message.chat.id}:data_limit")
985 else "Unlimited"
986 ),
987 (
988 mem_store.get(f"{message.chat.id}:expire_date").strftime("%Y-%m-%d")
989 if mem_store.get(f"{message.chat.id}:expire_date")
990 else "Never"
991 ),
992 ),
993 reply_markup=BotKeyboard.select_services(services, [], action="create"),
994 )
995
996
997@bot.callback_query_handler(query_startswith("switch_service:"), is_admin=True)
998async def switch_service(call: types.CallbackQuery):
999 if not (username := mem_store.get(f"{call.message.chat.id}:username")):
1000 return await bot.answer_callback_query(
1001 call.id, "❌ No user selected.", show_alert=True
1002 )
1003 selected_services: List[int] = mem_store.get(f"{call.message.chat.id}:services", [])
1004 _, service, action = call.data.split(":")
1005 service = int(service)
1006
1007 with GetDB() as db:
1008 services = crud.get_services(db)
1009 if service in selected_services:
1010 selected_services.remove(service)
1011 else:
1012 selected_services.append(service)
1013
1014 mem_store.set(f"{call.message.chat.id}:services", selected_services)
1015
1016 if action in ["edit"]:
1017 return await bot.edit_message_text(
1018 call.message.text,
1019 call.message.chat.id,
1020 call.message.message_id,
1021 reply_markup=BotKeyboard.select_services(
1022 services,
1023 selected_services,
1024 "edit",
1025 username=username,
1026 data_limit=mem_store.get(f"{call.message.chat.id}:data_limit"),
1027 expire_date=mem_store.get(f"{call.message.chat.id}:expire_date"),
1028 ),
1029 )
1030 await bot.edit_message_text(
1031 call.message.text,
1032 call.message.chat.id,
1033 call.message.message_id,
1034 reply_markup=BotKeyboard.select_services(services, selected_services, "create"),
1035 )
1036
1037
1038@bot.callback_query_handler(query_startswith("confirm:"), is_admin=True)
1039async def confirm_user_command(call: types.CallbackQuery):
1040 data = call.data.split(":")[1]
1041 chat_id = call.from_user.id
1042 full_name = call.from_user.full_name
1043 now = datetime.now()
1044 today = datetime(
1045 year=now.year, month=now.month, day=now.day, hour=23, minute=59, second=59
1046 )
1047
1048
1049@bot.callback_query_handler(query_startswith("do_delete"), is_admin=True)
1050async def delete_user(call: types.CallbackQuery):
1051 username = call.data.split(":")[1]
1052 with GetDB() as db:
1053 db_user = crud.get_user(db, username)
1054 crud.remove_user(db, db_user)
1055 await xray.operations.remove_user(db_user)
1056
1057 await bot.edit_message_text(
1058 "✅ User deleted.",
1059 call.message.chat.id,
1060 call.message.message_id,
1061 reply_markup=BotKeyboard.main_menu(),
1062 )
1063 if TELEGRAM_LOGGER_CHANNEL_ID:
1064 text = f"""\
1065🗑 <b>#Deleted #From_Bot</b>
1066➖➖➖➖➖➖➖➖➖
1067<b>Username :</b> <code>{db_user.username}</code>
1068<b>Traffic Limit :</b> <code>{readable_size(db_user.data_limit) if db_user.data_limit else "Unlimited"}</code>
1069<b>Expire Date :</b> <code>\
1070{datetime.fromtimestamp(db_user.expire).strftime('%H:%M:%S %Y-%m-%d') if db_user.expire else "Never"}</code>
1071➖➖➖➖➖➖➖➖➖
1072<b>By :</b> <a href="tg://user?id={call.from_user.id}">{call.from_user.full_name}</a>"""
1073 try:
1074 await bot.send_message(TELEGRAM_LOGGER_CHANNEL_ID, text, "HTML")
1075 except:
1076 pass
1077
1078
1079@bot.callback_query_handler(query_startswith("do_suspend"), is_admin=True)
1080async def disable_user(call: types.CallbackQuery):
1081 username = call.data.split(":")[1]
1082 with GetDB() as db:
1083 db_user = crud.get_user(db, username)
1084 crud.update_user(
1085 db, db_user, UserModify(username=username, status=UserStatusModify.disabled)
1086 )
1087 await xray.operations.remove_user(db_user)
1088 user = UserResponse.model_validate(db_user)
1089 await bot.edit_message_text(
1090 get_user_info_text(
1091 status="disabled",
1092 username=username,
1093 sub_url=user.subscription_url,
1094 data_limit=db_user.data_limit,
1095 usage=db_user.used_traffic,
1096 expire=db_user.expire,
1097 note=user.note,
1098 ),
1099 call.message.chat.id,
1100 call.message.message_id,
1101 parse_mode="HTML",
1102 reply_markup=BotKeyboard.user_menu(
1103 user_info={"status": "disabled", "username": db_user.username}, note=True
1104 ),
1105 )
1106 if TELEGRAM_LOGGER_CHANNEL_ID:
1107 text = f"""\
1108❌ <b>#Disabled #From_Bot</b>
1109➖➖➖➖➖➖➖➖➖
1110<b>Username</b> : <code>{username}</code>
1111➖➖➖➖➖➖➖➖➖
1112<b>By :</b> <a href="tg://user?id={call.from_user.id}">{call.from_user.full_name}</a>"""
1113 try:
1114 await bot.send_message(TELEGRAM_LOGGER_CHANNEL_ID, text, "HTML")
1115 except:
1116 pass
1117
1118
1119@bot.callback_query_handler(query_startswith("do_activate"), is_admin=True)
1120async def activate_user(call: types.CallbackQuery):
1121 username = call.data.split(":")[1]
1122 with GetDB() as db:
1123 db_user = crud.get_user(db, username)
1124 crud.update_user(
1125 db, db_user, UserModify(username=username, status=UserStatusModify.active)
1126 )
1127 await xray.operations.add_user(db_user)
1128 user = UserResponse.model_validate(db_user)
1129 await bot.edit_message_text(
1130 get_user_info_text(
1131 status="active",
1132 username=username,
1133 sub_url=user.subscription_url,
1134 data_limit=db_user.data_limit,
1135 usage=db_user.used_traffic,
1136 expire=db_user.expire,
1137 note=user.note,
1138 ),
1139 call.message.chat.id,
1140 call.message.message_id,
1141 parse_mode="HTML",
1142 reply_markup=BotKeyboard.user_menu(
1143 user_info={"status": "active", "username": db_user.username}, note=True
1144 ),
1145 )
1146 if TELEGRAM_LOGGER_CHANNEL_ID:
1147 text = f"""\
1148✅ <b>#Activated #From_Bot</b>
1149➖➖➖➖➖➖➖➖➖
1150<b>Username</b> : <code>{username}</code>
1151➖➖➖➖➖➖➖➖➖
1152<b>By :</b> <a href="tg://user?id={call.from_user.id}">{call.from_user.full_name}</a>"""
1153 try:
1154 await bot.send_message(TELEGRAM_LOGGER_CHANNEL_ID, text, "HTML")
1155 except:
1156 pass
1157
1158
1159@bot.callback_query_handler(query_startswith("do_reset_usage"), is_admin=True)
1160async def reset_all_usages(call: types.CallbackQuery):
1161 username = call.data.split(":")[1]
1162 with GetDB() as db:
1163 db_user = crud.get_user(db, username)
1164 crud.reset_user_data_usage(db, db_user)
1165 user = UserResponse.model_validate(db_user)
1166 await bot.edit_message_text(
1167 get_user_info_text(
1168 status=user.status,
1169 username=username,
1170 sub_url=user.subscription_url,
1171 data_limit=user.data_limit,
1172 usage=user.used_traffic,
1173 expire=user.expire,
1174 note=user.note,
1175 ),
1176 call.message.chat.id,
1177 call.message.message_id,
1178 parse_mode="HTML",
1179 reply_markup=BotKeyboard.user_menu(
1180 user_info={"status": user.status, "username": user.username}, note=True
1181 ),
1182 )
1183 if TELEGRAM_LOGGER_CHANNEL_ID:
1184 text = f"""\
1185🔁 <b>#Reset_usage #From_Bot</b>
1186➖➖➖➖➖➖➖➖➖
1187<b>Username</b> : <code>{username}</code>
1188➖➖➖➖➖➖➖➖➖
1189<b>By :</b> <a href="tg://user?id={call.from_user.id}">{call.from_user.full_name}</a>"""
1190 try:
1191 await bot.send_message(TELEGRAM_LOGGER_CHANNEL_ID, text, "HTML")
1192 except:
1193 pass
1194
1195
1196@bot.callback_query_handler(query_equals("do_restart"), is_admin=True)
1197async def restart_xray(call: types.CallbackQuery):
1198 m = await bot.edit_message_text(
1199 "🔄 Restarting XRay core...", call.message.chat.id, call.message.message_id
1200 )
1201 await xray.core.restart(xray.config.include_db_users())
1202 for node_id, node in list(xray.nodes.items()):
1203 if await node.is_healthy():
1204 await xray.operations.restart_node(node_id, xray.config.include_db_users())
1205 await bot.edit_message_text(
1206 "✅ XRay core restarted successfully.",
1207 m.chat.id,
1208 m.message_id,
1209 reply_markup=BotKeyboard.main_menu(),
1210 )
1211
1212
1213@bot.callback_query_handler(query_equals("edit_user_done"), is_admin=True)
1214async def edit_user(call: types.CallbackQuery):
1215 if (username := mem_store.get(f"{call.message.chat.id}:username")) is None:
1216 try:
1217 await bot.delete_message(call.message.chat.id, call.message.message_id)
1218 except Exception:
1219 pass
1220 return await bot.send_message(
1221 call.message.chat.id,
1222 "❌ Bot reload detected. Please start over.",
1223 reply_markup=BotKeyboard.main_menu(),
1224 )
1225
1226 if not mem_store.get(f"{call.message.chat.id}:services"):
1227 return await bot.answer_callback_query(
1228 call.id, "❌ No Service selected.", show_alert=True
1229 )
1230
1231 # inbounds: dict[str, list[str]] = {
1232 # k: v for k, v in mem_store.get(f'{call.message.chat.id}:protocols').items() if v}
1233 services: List[int] = mem_store.get(f"{call.message.chat.id}:services")
1234
1235 with GetDB() as db:
1236 db_user = crud.get_user(db, username)
1237 if not db_user:
1238 return await bot.answer_callback_query(
1239 call.id, text=f"User not found!", show_alert=True
1240 )
1241
1242 # proxies = {p.type.value: p.settings for p in db_user.proxies}
1243
1244 """for protocol in xray.config.inbounds_by_protocol:
1245 if protocol in inbounds and protocol not in db_user.inbounds:
1246 proxies.update({protocol: {'flow': TELEGRAM_DEFAULT_VLESS_FLOW} if
1247 TELEGRAM_DEFAULT_VLESS_FLOW and protocol == ProxyTypes.VLESS else {}})
1248 elif protocol in db_user.inbounds and protocol not in inbounds:
1249 del proxies[protocol]"""
1250
1251 modify = UserModify(
1252 username=username,
1253 # status=db_user.status,
1254 expire=(
1255 int(mem_store.get(f"{call.message.chat.id}:expire_date").timestamp())
1256 if mem_store.get(f"{call.message.chat.id}:expire_date")
1257 else 0
1258 ),
1259 data_limit=mem_store.get(f"{call.message.chat.id}:data_limit"),
1260 services=services,
1261 )
1262 last_user = UserResponse.model_validate(db_user)
1263 db_user = crud.update_user(db, db_user, modify)
1264
1265 user = UserResponse.model_validate(db_user)
1266
1267 if user.status == UserStatus.active:
1268 await xray.operations.update_user(db_user)
1269 else:
1270 await xray.operations.remove_user(db_user)
1271
1272 await bot.answer_callback_query(call.id, "✅ User updated successfully.")
1273
1274 text = get_user_info_text(
1275 status=user.status,
1276 username=user.username,
1277 sub_url=user.subscription_url,
1278 data_limit=user.data_limit,
1279 usage=user.used_traffic,
1280 expire=user.expire,
1281 note=user.note,
1282 )
1283 await bot.edit_message_text(
1284 text,
1285 call.message.chat.id,
1286 call.message.message_id,
1287 parse_mode="HTML",
1288 reply_markup=BotKeyboard.user_menu(
1289 {"username": db_user.username, "status": db_user.status}, note=True
1290 ),
1291 )
1292 if TELEGRAM_LOGGER_CHANNEL_ID:
1293 tag = f'\n➖➖➖➖➖➖➖➖➖ \n<b>By :</b> <a href="tg://user?id={call.from_user.id}">{call.from_user.full_name}</a>'
1294 if last_user.data_limit != user.data_limit:
1295 text = f"""\
1296📶 <b>#Traffic_Change #From_Bot</b>
1297➖➖➖➖➖➖➖➖➖
1298<b>Username :</b> <code>{user.username}</code>
1299<b>Last Traffic Limit :</b> <code>{readable_size(last_user.data_limit) if last_user.data_limit else "Unlimited"}</code>
1300<b>New Traffic Limit :</b> <code>{readable_size(user.data_limit) if user.data_limit else "Unlimited"}</code>{tag}"""
1301 try:
1302 await bot.send_message(TELEGRAM_LOGGER_CHANNEL_ID, text, "HTML")
1303 except:
1304 pass
1305 if last_user.expire != user.expire:
1306 text = f"""\
1307📅 <b>#Expiry_Change #From_Bot</b>
1308➖➖➖➖➖➖➖➖➖
1309<b>Username :</b> <code>{user.username}</code>
1310<b>Last Expire Date :</b> <code>\
1311{datetime.fromtimestamp(last_user.expire).strftime('%H:%M:%S %Y-%m-%d') if last_user.expire else "Never"}</code>
1312<b>New Expire Date :</b> <code>\
1313{datetime.fromtimestamp(user.expire).strftime('%H:%M:%S %Y-%m-%d') if user.expire else "Never"}</code>{tag}"""
1314 try:
1315 await bot.send_message(TELEGRAM_LOGGER_CHANNEL_ID, text, "HTML")
1316 except:
1317 pass
1318 if list(last_user.inbounds.values())[0] != list(user.inbounds.values())[0]:
1319 text = f"""\
1320⚙️ <b>#Inbounds_Change #From_Bot</b>
1321➖➖➖➖➖➖➖➖➖
1322<b>Username :</b> <code>{user.username}</code>
1323<b>Last Proxies :</b> <code>{", ".join(list(last_user.inbounds.values())[0])}</code>
1324<b>New Proxies :</b> <code>{", ".join(list(user.inbounds.values())[0])}</code>{tag}"""
1325 try:
1326 await bot.send_message(TELEGRAM_LOGGER_CHANNEL_ID, text, "HTML")
1327 except:
1328 pass
1329
1330
1331@bot.callback_query_handler(query_startswith("add_user_done"), is_admin=True)
1332async def delete_limpired(call: types.CallbackQuery):
1333 if mem_store.get(f"{call.message.chat.id}:username") is None:
1334 try:
1335 await bot.delete_message(call.message.chat.id, call.message.message_id)
1336 except Exception:
1337 pass
1338 return await bot.send_message(
1339 call.message.chat.id,
1340 "❌ Bot reload detected. Please start over.",
1341 reply_markup=BotKeyboard.main_menu(),
1342 )
1343
1344 if not mem_store.get(f"{call.message.chat.id}:services"):
1345 return await bot.answer_callback_query(
1346 call.id, "❌ No services selected.", show_alert=True
1347 )
1348
1349 services: List[int] = mem_store.get(f"{call.message.chat.id}:services", [])
1350
1351 new_user = UserCreate(
1352 username=mem_store.get(f"{call.message.chat.id}:username"),
1353 expire=(
1354 int(mem_store.get(f"{call.message.chat.id}:expire_date").timestamp())
1355 if mem_store.get(f"{call.message.chat.id}:expire_date")
1356 else None
1357 ),
1358 data_limit=(
1359 mem_store.get(f"{call.message.chat.id}:data_limit")
1360 if mem_store.get(f"{call.message.chat.id}:data_limit")
1361 else None
1362 ),
1363 services=services,
1364 )
1365 try:
1366 with GetDB() as db:
1367 db_user = crud.create_user(db, new_user)
1368 services = db_user.services
1369 user = UserResponse.model_validate(db_user)
1370 except sqlalchemy.exc.IntegrityError:
1371 db.rollback()
1372 return await bot.answer_callback_query(
1373 call.id, "❌ Username already exists.", show_alert=True
1374 )
1375
1376 await xray.operations.add_user(db_user)
1377
1378 text = get_user_info_text(
1379 status=user.status,
1380 username=user.username,
1381 sub_url=user.subscription_url,
1382 data_limit=user.data_limit,
1383 usage=user.used_traffic,
1384 expire=user.expire,
1385 note=user.note,
1386 )
1387 await bot.edit_message_text(
1388 text,
1389 call.message.chat.id,
1390 call.message.message_id,
1391 parse_mode="HTML",
1392 reply_markup=BotKeyboard.user_menu(
1393 user_info={"status": user.status, "username": user.username}, note=True
1394 ),
1395 )
1396
1397 if TELEGRAM_LOGGER_CHANNEL_ID:
1398 text = f"""\
1399🆕 <b>#Created #From_Bot</b>
1400➖➖➖➖➖➖➖➖➖
1401<b>Username :</b> <code>{user.username}</code>
1402<b>Traffic Limit :</b> <code>{readable_size(user.data_limit) if user.data_limit else "Unlimited"}</code>
1403<b>Expire Date :</b> <code>\
1404{datetime.fromtimestamp(user.expire).strftime('%H:%M:%S %Y-%m-%d') if user.expire else "Never"}</code>
1405<b>Services :</b> <code>{"" if not services else ", ".join([s.name for s in services])}</code>
1406➖➖➖➖➖➖➖➖➖
1407<b>By :</b> <a href="tg://user?id={call.from_user.id}">{call.from_user.full_name}</a>"""
1408 try:
1409 await bot.send_message(TELEGRAM_LOGGER_CHANNEL_ID, text, "HTML")
1410 except:
1411 pass
1412
1413
1414@bot.callback_query_handler(
1415 query_startswith(["do_delete_expired", "do_delete_limited"]), is_admin=True
1416)
1417async def delete_limpired(call: types.CallbackQuery):
1418 await bot.edit_message_text(
1419 "⏳ <b>In Progress...</b>",
1420 call.message.chat.id,
1421 call.message.message_id,
1422 parse_mode="HTML",
1423 )
1424 with GetDB() as db:
1425 depleted_users = crud.get_users(
1426 db,
1427 status=[
1428 UserStatus.limited if data == "delete_limited" else UserStatus.expired
1429 ],
1430 )
1431 file_name = f"{data[8:]}_users_{int(now.timestamp()*1000)}.txt"
1432 with open(file_name, "w") as f:
1433 f.write("USERNAME\tEXIPRY\tUSAGE/LIMIT\tSTATUS\n")
1434 deleted = 0
1435 for user in depleted_users:
1436 try:
1437 crud.remove_user(db, user)
1438 await xray.operations.remove_user(user)
1439 deleted += 1
1440 f.write(
1441 f'{user.username}\
1442\t{datetime.fromtimestamp(user.expire) if user.expire else "never"}\
1443\t{readable_size(user.used_traffic) if user.used_traffic else 0}\
1444/{readable_size(user.data_limit) if user.data_limit else "Unlimited"}\
1445\t{user.status}\n'
1446 )
1447 except:
1448 db.rollback()
1449 await bot.edit_message_text(
1450 f"✅ <code>{deleted}</code>/<code>{len(depleted_users)}</code> <b>{data[7:].title()} Users Deleted</b>",
1451 call.message.chat.id,
1452 call.message.message_id,
1453 parse_mode="HTML",
1454 reply_markup=BotKeyboard.main_menu(),
1455 )
1456 if TELEGRAM_LOGGER_CHANNEL_ID:
1457 text = f"""\
1458🗑 <b>#Delete #{data[7:].title()} #From_Bot</b>
1459➖➖➖➖➖➖➖➖➖
1460<b>Count:</b> <code>{deleted}</code>
1461➖➖➖➖➖➖➖➖➖
1462<b>By :</b> <a href="tg://user?id={call.from_user.id}">{call.from_user.full_name}</a>"""
1463 try:
1464 await bot.send_document(
1465 TELEGRAM_LOGGER_CHANNEL_ID,
1466 open(file_name, "rb"),
1467 caption=text,
1468 parse_mode="HTML",
1469 )
1470 os.remove(file_name)
1471 except:
1472 pass
1473
1474
1475@bot.callback_query_handler(query_startswith("do_add_data:"), is_admin=True)
1476async def add_data_to_all(call: types.CallbackQuery):
1477 schedule_delete_message(
1478 call.message.chat.id,
1479 (await bot.send_message(chat_id, "⏳ <b>In Progress...</b>", "HTML")).id,
1480 )
1481 data_limit = float(call.data.split(":")[1]) * 1024 * 1024 * 1024
1482 with GetDB() as db:
1483 users = crud.get_users(db)
1484 counter = 0
1485 file_name = f"new_data_limit_users_{int(now.timestamp()*1000)}.txt"
1486 with open(file_name, "w") as f:
1487 f.write("USERNAME\tEXIPRY\tUSAGE/LIMIT\tSTATUS\n")
1488 for user in users:
1489 try:
1490 if user.data_limit and user.status not in [
1491 UserStatus.limited,
1492 UserStatus.expired,
1493 ]:
1494 user = crud.update_user(
1495 db,
1496 user,
1497 UserModify(
1498 username=user.username,
1499 data_limit=(user.data_limit + data_limit),
1500 ),
1501 )
1502 counter += 1
1503 f.write(
1504 f'{user.username}\
1505\t{datetime.fromtimestamp(user.expire) if user.expire else "never"}\
1506\t{readable_size(user.used_traffic) if user.used_traffic else 0}\
1507/{readable_size(user.data_limit) if user.data_limit else "Unlimited"}\
1508\t{user.status}\n'
1509 )
1510 except:
1511 db.rollback()
1512 await cleanup_messages(chat_id)
1513 await bot.send_message(
1514 chat_id,
1515 f'✅ <b>{counter}/{len(users)} Users</b> Data Limit according to <code>{"+" if data_limit > 0 else "-"}{readable_size(abs(data_limit))}</code>',
1516 "HTML",
1517 reply_markup=BotKeyboard.main_menu(),
1518 )
1519 if TELEGRAM_LOGGER_CHANNEL_ID:
1520 text = f"""\
1521📶 <b>#Traffic_Change #From_Bot</b>
1522➖➖➖➖➖➖➖➖➖
1523<b>According to:</b> <code>{"+" if data_limit > 0 else "-"}{readable_size(abs(data_limit))}</code>
1524<b>Count:</b> <code>{counter}</code>
1525➖➖➖➖➖➖➖➖➖
1526<b>By :</b> <a href="tg://user?id={call.from_user.id}">{call.from_user.full_name}</a>"""
1527 try:
1528 await bot.send_document(
1529 TELEGRAM_LOGGER_CHANNEL_ID,
1530 open(file_name, "rb"),
1531 caption=text,
1532 parse_mode="HTML",
1533 )
1534 os.remove(file_name)
1535 except:
1536 pass
1537
1538
1539@bot.callback_query_handler(query_startswith("do_add_time:"), is_admin=True)
1540async def add_time_to_all(call: types.CallbackQuery):
1541 schedule_delete_message(
1542 call.message.chat.id,
1543 (await bot.send_message(chat_id, "⏳ <b>In Progress...</b>", "HTML")).id,
1544 )
1545 days = int(call.data.split(":")[1])
1546 with GetDB() as db:
1547 users = crud.get_users(db)
1548 counter = 0
1549 file_name = f"new_expiry_users_{int(now.timestamp()*1000)}.txt"
1550 with open(file_name, "w") as f:
1551 f.write("USERNAME\tEXIPRY\tUSAGE/LIMIT\tSTATUS\n")
1552 for user in users:
1553 try:
1554 if user.expire and user.status not in [
1555 UserStatus.limited,
1556 UserStatus.expired,
1557 ]:
1558 user = crud.update_user(
1559 db,
1560 user,
1561 UserModify(
1562 username=user.username,
1563 expire=int(
1564 (
1565 datetime.fromtimestamp(user.expire)
1566 + relativedelta(days=days)
1567 ).timestamp()
1568 ),
1569 ),
1570 )
1571 counter += 1
1572 f.write(
1573 f'{user.username}\
1574\t{datetime.fromtimestamp(user.expire) if user.expire else "never"}\
1575\t{readable_size(user.used_traffic) if user.used_traffic else 0}\
1576/{readable_size(user.data_limit) if user.data_limit else "Unlimited"}\
1577\t{user.status}\n'
1578 )
1579 except:
1580 db.rollback()
1581 await cleanup_messages(chat_id)
1582 await bot.send_message(
1583 chat_id,
1584 f"✅ <b>{counter}/{len(users)} Users</b> Expiry Changes according to {days} Days",
1585 "HTML",
1586 reply_markup=BotKeyboard.main_menu(),
1587 )
1588 if TELEGRAM_LOGGER_CHANNEL_ID:
1589 text = f"""\
1590📅 <b>#Expiry_Change #From_Bot</b>
1591➖➖➖➖➖➖➖➖➖
1592<b>According to:</b> <code>{days} Days</code>
1593<b>Count:</b> <code>{counter}</code>
1594➖➖➖➖➖➖➖➖➖
1595<b>By :</b> <a href="tg://user?id={call.from_user.id}">{call.from_user.full_name}</a>"""
1596 try:
1597 await bot.send_document(
1598 TELEGRAM_LOGGER_CHANNEL_ID,
1599 open(file_name, "rb"),
1600 caption=text,
1601 parse_mode="HTML",
1602 )
1603 os.remove(file_name)
1604 except:
1605 pass
1606
1607
1608@bot.callback_query_handler(query_startswith("do_revoke:"), is_admin=True)
1609async def revoke_sub(call: types.CallbackQuery):
1610 username = call.data.split(":")[1]
1611 with GetDB() as db:
1612 db_user = crud.get_user(db, username)
1613 if not db_user:
1614 return await bot.answer_callback_query(
1615 call.id, text=f"User not found!", show_alert=True
1616 )
1617 db_user = crud.revoke_user_sub(db, db_user)
1618 user = UserResponse.model_validate(db_user)
1619 text = get_user_info_text(
1620 status=user.status,
1621 username=user.username,
1622 sub_url=user.subscription_url,
1623 expire=user.expire,
1624 data_limit=user.data_limit,
1625 usage=user.used_traffic,
1626 note=user.note,
1627 )
1628 await bot.edit_message_text(
1629 f"✅ Subscription Successfully Revoked!\n\n{text}",
1630 call.message.chat.id,
1631 call.message.message_id,
1632 parse_mode="HTML",
1633 reply_markup=BotKeyboard.user_menu(
1634 user_info={"status": user.status, "username": user.username}, note=True
1635 ),
1636 )
1637
1638 if TELEGRAM_LOGGER_CHANNEL_ID:
1639 text = f"""\
1640🚫 <b>#Revoke_sub #From_Bot</b>
1641➖➖➖➖➖➖➖➖➖
1642<b>Username:</b> <code>{username}</code>
1643➖➖➖➖➖➖➖➖➖
1644<b>By :</b> <a href="tg://user?id={call.from_user.id}">{call.from_user.full_name}</a>"""
1645 try:
1646 await bot.send_message(TELEGRAM_LOGGER_CHANNEL_ID, text, "HTML")
1647 except:
1648 pass
1649
1650
1651@bot.message_handler(func=lambda message: True, is_admin=True)
1652async def search(message: types.Message):
1653 with GetDB() as db:
1654 db_user = crud.get_user(db, message.text)
1655 if not db_user:
1656 return await bot.reply_to(message, "❌ User not found.")
1657 user = UserResponse.model_validate(db_user)
1658 text = get_user_info_text(
1659 status=user.status,
1660 username=user.username,
1661 sub_url=user.subscription_url,
1662 expire=user.expire,
1663 data_limit=user.data_limit,
1664 usage=user.used_traffic,
1665 note=user.note,
1666 )
1667 return await bot.reply_to(
1668 message,
1669 text,
1670 parse_mode="html",
1671 reply_markup=BotKeyboard.user_menu(
1672 user_info={"status": user.status, "username": user.username}, note=True
1673 ),
1674 )