gitscope
← marzneshin / v0.1.0 / app/telegram/handlers/admin.py
https://dev.kronoz.win/r/marzneshin.git
app/telegram/handlers/admin.py · Python
Blame Raw
   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))