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