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