diff --git a/app/marznode/operations.py b/app/marznode/operations.py
index 815cf4c..5060903 100644
--- a/app/marznode/operations.py
+++ b/app/marznode/operations.py
@@ -1,9 +1,9 @@
from collections import defaultdict
from typing import TYPE_CHECKING
-from .grpclib import MarzNodeGRPCLIB
-from .grpcio import MarzNodeGRPCIO
from app import marznode
+from .grpcio import MarzNodeGRPCIO
+from .grpclib import MarzNodeGRPCLIB
from ..models.node import NodeConnectionBackend
from ..models.user import UserStatus
diff --git a/app/telegram/__init__.py b/app/telegram/__init__.py
index 5594627..5746191 100644
--- a/app/telegram/__init__.py
+++ b/app/telegram/__init__.py
@@ -1,42 +1,28 @@
-import importlib.util
-from os.path import dirname
-from config import TELEGRAM_API_TOKEN, TELEGRAM_PROXY_URL
-from telebot.async_telebot import AsyncTeleBot
from telebot import asyncio_helper
+from telebot.async_telebot import AsyncTeleBot
+
+from config import TELEGRAM_API_TOKEN, TELEGRAM_PROXY_URL
+from app.telegram.report import (
+ report,
+ report_new_user,
+ report_user_modification,
+ report_user_deletion,
+ report_status_change,
+ report_user_usage_reset,
+ report_user_subscription_revoked,
+)
bot = None
if TELEGRAM_API_TOKEN:
asyncio_helper.proxy = TELEGRAM_PROXY_URL
bot = AsyncTeleBot(TELEGRAM_API_TOKEN)
-handler_names = ["admin", "user"]
-
async def start_bot():
if bot:
- handler_dir = dirname(__file__) + "/handlers/"
- for name in handler_names:
- spec = importlib.util.spec_from_file_location(
- name, f"{handler_dir}{name}.py"
- )
- spec.loader.exec_module(importlib.util.module_from_spec(spec))
-
- from app.telegram import utils # setup custom handlers
-
- utils.setup()
await bot.polling()
-from .handlers.report import ( # noqa
- report,
- report_new_user,
- report_user_modification,
- report_user_deletion,
- report_status_change,
- report_user_usage_reset,
- report_user_subscription_revoked,
-)
-
__all__ = [
"bot",
"report",
diff --git a/app/telegram/handlers/admin.py b/app/telegram/handlers/admin.py
deleted file mode 100644
index 13e070d..0000000
--- a/app/telegram/handlers/admin.py
+++ /dev/null
@@ -1,1674 +0,0 @@
-import io
-import math
-import re
-import random
-import string
-import os
-from datetime import datetime
-
-import qrcode
-import sqlalchemy
-from dateutil.relativedelta import relativedelta
-from telebot import types
-from telebot.util import user_link
-from telebot.asyncio_handler_backends import State, StatesGroup
-
-from app.db import GetDB, crud
-from app.models.user import (
- UserCreate,
- UserModify,
- UserResponse,
- UserStatus,
- UserStatusModify,
-)
-from app.models.proxy import ProxyTypes
-from app.telegram import bot
-from app.telegram.utils.custom_filters import query_equals, query_startswith
-from app.telegram.utils.keyboard import BotKeyboard
-from app.utils.store import MemoryStorage
-from app.utils.system import cpu_usage, memory_usage, readable_size
-
-try:
- from app.utils.system import realtime_bandwith as realtime_bandwidth
-except ImportError:
- from app.utils.system import realtime_bandwidth
-
-from config import TELEGRAM_LOGGER_CHANNEL_ID, TELEGRAM_DEFAULT_VLESS_FLOW
-
-mem_store = MemoryStorage()
-
-
-class botStates(StatesGroup):
- start = State()
- add_data = State() # statesgroup should contain states
- add_time = State()
- edit_user_data_limit = State()
- edit_user_expire = State()
- edit_note = State()
- # add_user_from_template = State()
- add_user_username = State()
- add_user_data_limit = State()
- add_user_expire = State()
-
-
-def get_system_info():
- mem = memory_usage()
- cpu = cpu_usage()
- with GetDB() as db:
- bandwidth = crud.get_system_usage(db)
- total_users = crud.get_users_count(db)
- users_active = crud.get_users_count(db, UserStatus.active)
- return """\
-π *CPU Cores*: `{cpu_cores}`
-π₯ *CPU Usage*: `{cpu_percent}%`
-βββββββ
-π *Total Memory*: `{total_memory}`
-π *In Use Memory*: `{used_memory}`
-π *Free Memory*: `{free_memory}`
-βββββββ
-β¬οΈ *Download Usage*: `{down_bandwidth}`
-β¬οΈ *Upload Usage*: `{up_bandwidth}`
-βοΈ *Total Usage*: `{total_bandwidth}`
-βββββββ
-π₯ *Total Users*: `{total_users}`
-π’ *Active Users*: `{active_users}`
-π΄ *Deactivate Users*: `{deactivate_users}`
-βββββββ
-β« *Upload Speed*: `{up_speed}`
-β¬ *Download Speed*: `{down_speed}`
-""".format(
- cpu_cores=cpu.cores,
- cpu_percent=cpu.percent,
- total_memory=readable_size(mem.total),
- used_memory=readable_size(mem.used),
- free_memory=readable_size(mem.free),
- total_bandwidth=readable_size(bandwidth.uplink + bandwidth.downlink),
- up_bandwidth=readable_size(bandwidth.uplink),
- down_bandwidth=readable_size(bandwidth.downlink),
- total_users=total_users,
- active_users=users_active,
- deactivate_users=total_users - users_active,
- up_speed=readable_size(realtime_bandwidth().outgoing_bytes),
- down_speed=readable_size(realtime_bandwidth().incoming_bytes),
- )
-
-
-def schedule_delete_message(chat_id, *message_ids: int) -> None:
- messages: list[int] = mem_store.get(f"{chat_id}:messages_to_delete", [])
- for mid in message_ids:
- messages.append(mid)
- mem_store.set(f"{chat_id}:messages_to_delete", messages)
-
-
-async def cleanup_messages(chat_id: int) -> None:
- messages: list[int] = mem_store.get(f"{chat_id}:messages_to_delete", [])
- for message_id in messages:
- try:
- await bot.delete_message(chat_id, message_id)
- except:
- pass
- mem_store.set(f"{chat_id}:messages_to_delete", [])
-
-
-@bot.message_handler(commands=["start", "help"], is_admin=True)
-async def help_command(message: types.Message):
- await cleanup_messages(message.chat.id)
- # bot.clear_step_handler_by_chat_id(message.chat.id)
- await bot.delete_state(message.from_user.id, message.chat.id)
- return await bot.reply_to(
- message,
- """
-{user_link} Welcome to Marzban Telegram-Bot Admin Panel.
-Here you can manage your users and proxies.
-To get started, use the buttons below.
-""".format(
- user_link=user_link(message.from_user)
- ),
- parse_mode="html",
- reply_markup=BotKeyboard.main_menu(),
- )
-
-
-@bot.callback_query_handler(query_equals("system"), is_admin=True)
-async def system_command(call: types.CallbackQuery):
- return await bot.edit_message_text(
- get_system_info(),
- call.message.chat.id,
- call.message.message_id,
- parse_mode="MarkdownV2",
- reply_markup=BotKeyboard.main_menu(),
- )
-
-
-@bot.callback_query_handler(query_equals("restart"), is_admin=True)
-async def restart_command(call: types.CallbackQuery):
- await bot.edit_message_text(
- "β οΈ Are you sure? This will restart Xray core.",
- call.message.chat.id,
- call.message.message_id,
- reply_markup=BotKeyboard.confirm_action(action="do_restart"),
- )
-
-
-@bot.callback_query_handler(query_startswith("delete:"), is_admin=True)
-async def delete_user_command(call: types.CallbackQuery):
- username = call.data.split(":")[1]
- await bot.edit_message_text(
- f"β οΈ Are you sure? This will delete user `{username}`.",
- call.message.chat.id,
- call.message.message_id,
- parse_mode="markdown",
- reply_markup=BotKeyboard.confirm_action(action="do_delete", username=username),
- )
-
-
-@bot.callback_query_handler(query_startswith("suspend:"), is_admin=True)
-async def suspend_user_command(call: types.CallbackQuery):
- username = call.data.split(":")[1]
- await bot.edit_message_text(
- f"β οΈ Are you sure? This will suspend user `{username}`.",
- call.message.chat.id,
- call.message.message_id,
- parse_mode="markdown",
- reply_markup=BotKeyboard.confirm_action(action="do_suspend", username=username),
- )
-
-
-@bot.callback_query_handler(query_startswith("activate:"), is_admin=True)
-async def activate_user_command(call: types.CallbackQuery):
- username = call.data.split(":")[1]
- await bot.edit_message_text(
- f"β οΈ Are you sure? This will activate user `{username}`.",
- call.message.chat.id,
- call.message.message_id,
- parse_mode="markdown",
- reply_markup=BotKeyboard.confirm_action(
- action="do_activate", username=username
- ),
- )
-
-
-@bot.callback_query_handler(query_startswith("reset_usage:"), is_admin=True)
-async def reset_usage_user_command(call: types.CallbackQuery):
- username = call.data.split(":")[1]
- await bot.edit_message_text(
- f"β οΈ Are you sure? This will Reset Usage of user `{username}`.",
- call.message.chat.id,
- call.message.message_id,
- parse_mode="markdown",
- reply_markup=BotKeyboard.confirm_action(
- action="do_reset_usage", username=username
- ),
- )
-
-
-@bot.callback_query_handler(query_equals("edit_all"), is_admin=True)
-async def edit_all_command(call: types.CallbackQuery):
- with GetDB() as db:
- total_users = crud.get_users_count(db)
- active_users = crud.get_users_count(db, UserStatus.active)
- disabled_users = crud.get_users_count(db, UserStatus.disabled)
- expired_users = crud.get_users_count(db, UserStatus.expired)
- limited_users = crud.get_users_count(db, UserStatus.limited)
- text = f"""
-π₯ *Total Users*: `{total_users}`
-β
*Active Users*: `{active_users}`
-β *Disabled Users*: `{disabled_users}`
-π° *Expired Users*: `{expired_users}`
-πͺ« *Limited Users*: `{limited_users}`"""
- return await bot.edit_message_text(
- text,
- call.message.chat.id,
- call.message.message_id,
- parse_mode="markdown",
- reply_markup=BotKeyboard.edit_all_menu(),
- )
-
-
-@bot.callback_query_handler(query_equals("delete_expired"), is_admin=True)
-async def delete_expired_command(call: types.CallbackQuery):
- await bot.edit_message_text(
- f"β οΈ Are you sure? This will *DELETE All Expired Users*βΌοΈ",
- call.message.chat.id,
- call.message.message_id,
- parse_mode="markdown",
- reply_markup=BotKeyboard.confirm_action(action="do_delete_expired"),
- )
-
-
-@bot.callback_query_handler(query_equals("delete_limited"), is_admin=True)
-async def delete_limited_command(call: types.CallbackQuery):
- await bot.edit_message_text(
- f"β οΈ Are you sure? This will *DELETE All Limited Users*βΌοΈ",
- call.message.chat.id,
- call.message.message_id,
- parse_mode="markdown",
- reply_markup=BotKeyboard.confirm_action(action="do_delete_limited"),
- )
-
-
-@bot.callback_query_handler(query_equals("add_data"), is_admin=True)
-async def add_data_command(call: types.CallbackQuery):
- msg = await bot.edit_message_text(
- f"π Enter Data Limit to increase or decrease (GB):",
- call.message.chat.id,
- call.message.message_id,
- reply_markup=BotKeyboard.inline_cancel_action(),
- )
- schedule_delete_message(call.message.chat.id, call.message.id)
- schedule_delete_message(call.message.chat.id, msg.id)
- # return bot.register_next_step_handler(call.message, add_data_step)
- await bot.set_state(call.from_user.id, botStates.add_data, call.message.chat.id)
-
-
-@bot.message_handler(state=botStates.add_data)
-async def add_data_step(message):
- try:
- data_limit = float(message.text)
- if not data_limit:
- raise ValueError
- except ValueError:
- wait_msg = await bot.send_message(
- message.chat.id, "β Data limit must be a number and not zero."
- )
- schedule_delete_message(message.chat.id, wait_msg.message_id)
- return # bot.register_next_step_handler(wait_msg, add_data_step)
- schedule_delete_message(message.chat.id, message.message_id)
- msg = await bot.send_message(
- message.chat.id,
- f"β οΈ Are you sure? this will change Data limit of all users according to "
- f"{'+' if data_limit > 0 else '-'}{readable_size(abs(data_limit *1024*1024*1024))}",
- parse_mode="html",
- reply_markup=BotKeyboard.confirm_action("do_add_data", data_limit),
- )
- await bot.delete_state(message.from_user.id, message.chat.id)
- await cleanup_messages(message.chat.id)
- schedule_delete_message(message.chat.id, msg.id)
-
-
-@bot.callback_query_handler(query_equals("add_time"), is_admin=True)
-async def add_time_command(call: types.CallbackQuery):
- msg = await bot.edit_message_text(
- f"π
Enter Days to increase or decrease expiry:",
- call.message.chat.id,
- call.message.message_id,
- reply_markup=BotKeyboard.inline_cancel_action(),
- )
- schedule_delete_message(call.message.chat.id, call.message.id)
- schedule_delete_message(call.message.chat.id, msg.id)
- # return bot.register_next_step_handler(call.message, add_time_step)
- await bot.set_state(call.from_user.id, botStates.add_time, call.message.chat.id)
-
-
-@bot.message_handler(state=botStates.add_time)
-async def add_time_step(message):
- try:
- days = int(message.text)
- if not days:
- raise ValueError
- except ValueError:
- wait_msg = await bot.send_message(
- message.chat.id, "β Days must be as a number and not zero."
- )
- schedule_delete_message(message.chat.id, wait_msg.message_id)
- return # bot.register_next_step_handler(wait_msg, add_time_step)
- schedule_delete_message(message.chat.id, message.message_id)
- msg = await bot.send_message(
- message.chat.id,
- f"β οΈ Are you sure? this will change Expiry Time of all users according to {days} Days",
- parse_mode="html",
- reply_markup=BotKeyboard.confirm_action("do_add_time", days),
- )
- await cleanup_messages(message.chat.id)
- schedule_delete_message(message.chat.id, msg.id)
-
-
-@bot.callback_query_handler(query_startswith("inbound"), is_admin=True)
-async def inbound_command(call: types.CallbackQuery):
- await bot.edit_message_text(
- f"Select inbound to *{call.data[8:].title()}* from all users",
- call.message.chat.id,
- call.message.message_id,
- parse_mode="markdown",
- reply_markup=BotKeyboard.inbounds_menu(call.data, xray.config.inbounds_by_tag),
- )
-
-
-@bot.callback_query_handler(query_startswith("confirm_inbound"), is_admin=True)
-async def delete_expired_confirm_command(call: types.CallbackQuery):
- await bot.edit_message_text(
- f"β οΈ Are you sure? This will *{call.data[16:].replace(':', ' ')} for All Users*βΌοΈ",
- call.message.chat.id,
- call.message.message_id,
- parse_mode="markdown",
- reply_markup=BotKeyboard.confirm_action(action=call.data[8:]),
- )
-
-
-@bot.callback_query_handler(query_startswith("edit:"), is_admin=True)
-async def edit_command(call: types.CallbackQuery):
- # bot.clear_step_handler_by_chat_id(call.message.chat.id)
- await bot.delete_state(call.from_user.id, call.message.chat.id)
- username = call.data.split(":")[1]
- with GetDB() as db:
- db_user = crud.get_user(db, username)
- if not db_user:
- return await bot.answer_callback_query(
- call.id, "β User not found.", show_alert=True
- )
- services = crud.get_services(db)
- user = UserResponse.model_validate(db_user)
- mem_store.set(f"{call.message.chat.id}:username", username)
- mem_store.set(f"{call.message.chat.id}:data_limit", db_user.data_limit)
- mem_store.set(
- f"{call.message.chat.id}:expire_date",
- datetime.fromtimestamp(db_user.expire) if db_user.expire else None,
- )
- mem_store.set(f"{call.message.chat.id}:services", [s.id for s in db_user.services])
- await bot.edit_message_text(
- f"π Editing user `{username}`",
- call.message.chat.id,
- call.message.message_id,
- parse_mode="markdown",
- reply_markup=BotKeyboard.select_services(
- services,
- [s.id for s in user.services],
- "edit",
- username=username,
- data_limit=db_user.data_limit,
- expire_date=mem_store.get(f"{call.message.chat.id}:expire_date"),
- ),
- )
-
-
-@bot.callback_query_handler(query_equals("help_edit"), is_admin=True)
-async def help_edit_command(call: types.CallbackQuery):
- await bot.answer_callback_query(
- call.id, text="Press the (βοΈ Edit) button to edit", show_alert=True
- )
-
-
-@bot.callback_query_handler(query_equals("cancel"), is_admin=True)
-async def cancel_command(call: types.CallbackQuery):
- # bot.clear_step_handler_by_chat_id(call.message.chat.id)
- await bot.delete_state(call.from_user.id, call.message.chat.id)
- return await bot.edit_message_text(
- get_system_info(),
- call.message.chat.id,
- call.message.message_id,
- parse_mode="MarkdownV2",
- reply_markup=BotKeyboard.main_menu(),
- )
-
-
-@bot.callback_query_handler(query_startswith("edit_user:"), is_admin=True)
-async def edit_user_command(call: types.CallbackQuery):
- _, username, action = call.data.split(":")
- schedule_delete_message(call.message.chat.id, call.message.id)
- await cleanup_messages(call.message.chat.id)
- if action == "data":
- msg = await bot.send_message(
- call.message.chat.id,
- "β¬οΈ Enter Data Limit (GB):\nβ οΈ Send 0 for unlimited.",
- reply_markup=BotKeyboard.inline_cancel_action(f"user:{username}"),
- )
- mem_store.set(f"{call.message.chat.id}:edit_msg_text", call.message.text)
- await bot.delete_state(call.from_user.id, call.message.chat.id)
- await bot.set_state(
- call.from_user.id, botStates.edit_user_data_limit, call.message.chat.id
- )
- async with bot.retrieve_data(call.from_user.id, call.message.chat.id) as data:
- data["username"] = username
- # bot.clear_step_handler_by_chat_id(call.message.chat.id)
- # bot.register_next_step_handler(
- # call.message, edit_user_data_limit_step, username)
- schedule_delete_message(call.message.chat.id, msg.message_id)
- elif action == "expire":
- msg = await bot.send_message(
- call.message.chat.id,
- "β¬οΈ Enter Expire Date (YYYY-MM-DD)\nOr You Can Use Regex Symbol: ^[0-9]{1,3}(M|D) :\nβ οΈ Send 0 for never expire.",
- reply_markup=BotKeyboard.inline_cancel_action(f"user:{username}"),
- )
- mem_store.set(f"{call.message.chat.id}:edit_msg_text", call.message.text)
- # bot.clear_step_handler_by_chat_id(call.message.chat.id)
- await bot.set_state(
- call.from_user.id, botStates.edit_user_expire, call.message.chat.id
- )
- async with bot.retrieve_data(call.from_user.id, call.message.chat.id) as data:
- data["username"] = username
- # bot.register_next_step_handler(
- # call.message, edit_user_expire_step, username=username)
- schedule_delete_message(call.message.chat.id, msg.message_id)
-
-
-@bot.message_handler(state=botStates.edit_user_data_limit)
-async def edit_user_data_limit_step(message: types.Message):
- try:
- if float(message.text) < 0:
- wait_msg = await bot.send_message(
- message.chat.id, "β Data limit must be greater or equal to 0."
- )
- schedule_delete_message(message.chat.id, wait_msg.message_id)
- return # bot.register_next_step_handler(wait_msg, edit_user_data_limit_step, username=username)
- data_limit = float(message.text) * 1024**3
- except ValueError:
- wait_msg = await bot.send_message(
- message.chat.id, "β Data limit must be a number."
- )
- schedule_delete_message(message.chat.id, wait_msg.message_id)
- return # bot.register_next_step_handler(wait_msg, edit_user_data_limit_step, username=username)
- mem_store.set(f"{message.chat.id}:data_limit", data_limit)
- schedule_delete_message(message.chat.id, message.message_id)
- text = mem_store.get(f"{message.chat.id}:edit_msg_text")
- mem_store.delete(f"{message.chat.id}:edit_msg_text")
- async with bot.retrieve_data(call.from_user.id, call.message.chat.id) as data:
- username = data["username"]
- del data["username"]
- await bot.send_message(
- message.chat.id,
- text or f"π Editing user {username}",
- parse_mode="html",
- reply_markup=BotKeyboard.select_protocols(
- mem_store.get(f"{message.chat.id}:protocols"),
- "edit",
- username=username,
- data_limit=data_limit,
- expire_date=mem_store.get(f"{message.chat.id}:expire_date"),
- ),
- )
- await cleanup_messages(message.chat.id)
-
-
-@bot.message_handler(state=botStates.edit_user_expire)
-async def edit_user_expire_step(message: types.Message):
- try:
- now = datetime.now()
- today = datetime(
- year=now.year, month=now.month, day=now.day, hour=23, minute=59, second=59
- )
- if re.match(r"^[0-9]{1,3}(M|m|D|d)$", message.text):
- expire_date = today
- number_pattern = r"^[0-9]{1,3}"
- number = int(re.findall(number_pattern, message.text)[0])
- symbol_pattern = r"(M|m|D|d)$"
- symbol = re.findall(symbol_pattern, message.text)[0].upper()
- if symbol == "M":
- expire_date = today + relativedelta(months=number)
- elif symbol == "D":
- expire_date = today + relativedelta(days=number)
- elif message.text != "0":
- expire_date = datetime.strptime(message.text, "%Y-%m-%d")
- else:
- expire_date = None
- if expire_date and expire_date < today:
- wait_msg = await bot.send_message(
- message.chat.id, "β Expire date must be greater than today."
- )
- schedule_delete_message(message.chat.id, wait_msg.message_id)
- return # bot.register_next_step_handler(wait_msg, edit_user_expire_step, username=username)
- except ValueError:
- 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)",
- )
- schedule_delete_message(message.chat.id, wait_msg.message_id)
- return # bot.register_next_step_handler(wait_msg, edit_user_expire_step, username=username)
-
- mem_store.set(f"{message.chat.id}:expire_date", expire_date)
- schedule_delete_message(message.chat.id, message.message_id)
- text = mem_store.get(f"{message.chat.id}:edit_msg_text")
- mem_store.delete(f"{message.chat.id}:edit_msg_text")
- async with bot.retrieve_data(call.from_user.id, call.message.chat.id) as data:
- username = data["username"]
- del data["username"]
- await bot.send_message(
- message.chat.id,
- text or f"π Editing user {username}",
- parse_mode="html",
- reply_markup=BotKeyboard.select_protocols(
- mem_store.get(f"{message.chat.id}:protocols"),
- "edit",
- username=username,
- data_limit=mem_store.get(f"{message.chat.id}:data_limit"),
- expire_date=expire_date,
- ),
- )
- await cleanup_messages(message.chat.id)
-
-
-@bot.callback_query_handler(query_startswith("users:"), is_admin=True)
-async def users_command(call: types.CallbackQuery):
- page = int(call.data.split(":")[1]) if len(call.data.split(":")) > 1 else 1
- with GetDB() as db:
- total_pages = math.ceil(crud.get_users_count(db) / 10)
- users = crud.get_users(
- db,
- offset=(page - 1) * 10,
- limit=10,
- sort=[crud.UsersSortingOptions["-created_at"]],
- )
- text = """π₯ Users: (Page {page}/{total_pages})
-β
Active
-β Disabled
-π° Expired
-πͺ« Limited""".format(
- page=page, total_pages=total_pages
- )
-
- await bot.edit_message_text(
- text,
- call.message.chat.id,
- call.message.message_id,
- parse_mode="HTML",
- reply_markup=BotKeyboard.user_list(users, page, total_pages=total_pages),
- )
-
-
-def get_user_info_text(
- status: str,
- username: str,
- sub_url: str,
- data_limit: int = None,
- usage: int = None,
- expire: int = None,
- note: str = None,
-) -> str:
- statuses = {"active": "β
", "expired": "π°", "limited": "πͺ«", "disabled": "β"}
- text = f"""\
-ββ{statuses[status]} Status: {status.title()}
-β ββUsername: {username}
-β
-ββπ Data limit: {readable_size(data_limit) if data_limit else 'Unlimited'}
-β ββData Used: {readable_size(usage) if usage else "-"}
-β
-ββπ
Expiry Date: {datetime.fromtimestamp(expire).date() if expire else 'Never'}
-β ββDays left: {(datetime.fromtimestamp(expire or 0) - datetime.now()).days if expire else '-'}
-β
-"""
- if note:
- text += f"ββπ Note: {note}\nβ\n"
- text += f'ββπ Subscription: {sub_url}'
- return text
-
-
-@bot.callback_query_handler(query_startswith("edit_note:"), is_admin=True)
-async def edit_note_command(call: types.CallbackQuery):
- username = call.data.split(":")[1]
- with GetDB() as db:
- db_user = crud.get_user(db, username)
- if not db_user:
- return await bot.answer_callback_query(
- call.id, "β User not found.", show_alert=True
- )
- schedule_delete_message(call.message.chat.id, call.message.id)
- await cleanup_messages(call.message.chat.id)
- msg = await bot.send_message(
- call.message.chat.id,
- f"π Current Note: {db_user.note}\n\nSend new Note for {username}",
- parse_mode="HTML",
- reply_markup=BotKeyboard.inline_cancel_action(f"user:{username}"),
- )
- mem_store.set(f"{call.message.chat.id}:username", username)
- schedule_delete_message(call.message.chat.id, msg.id)
- await bot.set_state(call.from_user.id, botStates.edit_note, call.message.chat.id)
- # bot.register_next_step_handler(msg, edit_note_step)
-
-
-@bot.message_handler(state=botStates.edit_note)
-async def edit_note_step(message: types.Message):
- note = message.text or ""
- if len(note) > 500:
- wait_msg = await bot.send_message(
- message.chat.id, "β Note can not be more than 500 characters."
- )
- schedule_delete_message(message.chat.id, wait_msg.id)
- schedule_delete_message(message.chat.id, message.id)
- return # bot.register_next_step_handler(wait_msg, edit_note_step)
- username = mem_store.get(f"{message.chat.id}:username")
- if not username:
- await cleanup_messages(message.chat.id)
- await bot.reply_to(message, "β Something went wrong!\n restart bot /start")
- with GetDB() as db:
- db_user = crud.get_user(db, username)
- last_note = db_user.note
- db_user = crud.update_user(
- db, db_user, UserModify(username=username, note=note)
- )
- user = UserResponse.model_validate(db_user)
- text = get_user_info_text(
- status=user.status,
- username=user.username,
- sub_url=user.subscription_url,
- expire=user.expire,
- data_limit=user.data_limit,
- usage=user.used_traffic,
- note=user.note,
- )
- await bot.reply_to(
- message,
- text,
- parse_mode="html",
- reply_markup=BotKeyboard.user_menu(
- user_info={"status": user.status, "username": user.username}, note=True
- ),
- )
- if TELEGRAM_LOGGER_CHANNEL_ID:
- text = f"""\
-π #Edit_Note #From_Bot
-βββββββββ
-Username : {username}
-Last Note : {last_note}
-New Note : {note}
-βββββββββ
-By : {message.from_user.full_name}"""
- try:
- await bot.send_message(TELEGRAM_LOGGER_CHANNEL_ID, text, "HTML")
- except:
- pass
-
-
-@bot.callback_query_handler(query_startswith("user:"), is_admin=True)
-async def user_command(call: types.CallbackQuery):
- await bot.delete_state(call.from_user.id, call.message.chat.id)
- # bot.clear_step_handler_by_chat_id(call.message.chat.id)
- username = call.data.split(":")[1]
- page = int(call.data.split(":")[2]) if len(call.data.split(":")) > 2 else 1
- with GetDB() as db:
- db_user = crud.get_user(db, username)
- if not db_user:
- return await bot.answer_callback_query(
- call.id, "β User not found.", show_alert=True
- )
- user = UserResponse.model_validate(db_user)
- text = get_user_info_text(
- status=user.status,
- username=username,
- sub_url=user.subscription_url,
- data_limit=user.data_limit,
- usage=user.used_traffic,
- expire=user.expire,
- note=user.note,
- )
- await bot.edit_message_text(
- text,
- call.message.chat.id,
- call.message.message_id,
- parse_mode="HTML",
- reply_markup=BotKeyboard.user_menu(
- {"username": user.username, "status": user.status}, page=page, note=True
- ),
- )
-
-
-@bot.callback_query_handler(query_startswith("revoke_sub:"), is_admin=True)
-async def revoke_sub_command(call: types.CallbackQuery):
- username = call.data.split(":")[1]
- await bot.edit_message_text(
- f"β οΈ Are you sure? This will *Revoke Subscription* link for `{username}`βΌοΈ",
- call.message.chat.id,
- call.message.message_id,
- parse_mode="markdown",
- reply_markup=BotKeyboard.confirm_action(action="do_revoke", username=username),
- )
-
-
-@bot.callback_query_handler(query_startswith("links:"), is_admin=True)
-async def links_command(call: types.CallbackQuery):
- username = call.data.split(":")[1]
-
- with GetDB() as db:
- db_user = crud.get_user(db, username)
- if not db_user:
- return await bot.answer_callback_query(
- call.id, "User not found!", show_alert=True
- )
-
- user = UserResponse.model_validate(db_user)
-
- text = f"{user.subscription_url}\n\n\n"
- for link in user.links:
- text += f"{link}\n\n"
-
- await bot.edit_message_text(
- text,
- call.message.chat.id,
- call.message.message_id,
- parse_mode="HTML",
- reply_markup=BotKeyboard.show_links(username),
- )
-
-
-@bot.callback_query_handler(query_startswith("genqr:"), is_admin=True)
-async def genqr_command(call: types.CallbackQuery):
- username = call.data.split(":")[1]
-
- with GetDB() as db:
- db_user = crud.get_user(db, username)
- if not db_user:
- return await bot.answer_callback_query(
- call.id, "User not found!", show_alert=True
- )
-
- user = UserResponse.model_validate(db_user)
-
- await bot.answer_callback_query(call.id, "Generating QR code...")
-
- for link in user.links:
- f = io.BytesIO()
- qr = qrcode.QRCode(border=6)
- qr.add_data(link)
- qr.make_image().save(f)
- f.seek(0)
- await bot.send_photo(
- call.message.chat.id,
- photo=f,
- caption=f"{link}",
- parse_mode="HTML",
- )
- with io.BytesIO() as f:
- qr = qrcode.QRCode(border=6)
- qr.add_data(user.subscription_url)
- qr.make_image().save(f)
- f.seek(0)
- await bot.send_photo(
- call.message.chat.id,
- photo=f,
- caption=get_user_info_text(
- status=user.status,
- username=user.username,
- sub_url=user.subscription_url,
- data_limit=user.data_limit,
- usage=user.used_traffic,
- expire=user.expire,
- ),
- parse_mode="HTML",
- reply_markup=BotKeyboard.subscription_page(user.subscription_url),
- )
- try:
- await bot.delete_message(call.message.chat.id, call.message.message_id)
- except:
- pass
-
- text = f"{user.subscription_url}\n\n\n"
- for link in user.links:
- text += f"{link}\n\n"
-
- await bot.send_message(
- call.message.chat.id,
- text,
- "HTML",
- reply_markup=BotKeyboard.show_links(username),
- )
-
-
-@bot.callback_query_handler(query_startswith("random"), is_admin=True)
-async def random_username(call: types.CallbackQuery):
- # bot.clear_step_handler_by_chat_id(call.message.chat.id)
- await bot.delete_state(call.from_user.id, call.message.chat.id)
- template_id = int(call.data.split(":")[1] or 0)
- mem_store.delete(f"{call.message.chat.id}:template_id")
-
- characters = string.ascii_letters + string.digits
- username = (
- random.choice(characters)
- + "".join(random.choices(characters, k=4))
- + "_"
- + "".join(random.choices(characters, k=4))
- )
-
- schedule_delete_message(call.message.chat.id, call.message.id)
- await cleanup_messages(call.message.chat.id)
-
- msg = await bot.send_message(
- call.message.chat.id,
- "β¬οΈ Enter Data Limit (GB):\nβ οΈ Send 0 for unlimited.",
- reply_markup=BotKeyboard.inline_cancel_action(),
- )
- schedule_delete_message(call.message.chat.id, msg.id)
- await bot.set_state(
- call.from_user.id, botStates.add_user_data_limit, call.message.chat.id
- )
- async with bot.retrieve_data(call.from_user.id, call.message.chat.id) as data:
- data["username"] = username
- return
-
-
-@bot.callback_query_handler(query_equals("add_user"), is_admin=True)
-async def add_user_command(call: types.CallbackQuery):
- try:
- await bot.delete_message(call.message.chat.id, call.message.message_id)
- except: # noqa
- pass
- username_msg = await bot.send_message(
- call.message.chat.id,
- "π€ Enter username:\nβ οΈUsername only can be 3 to 32 characters and contain a-z, A-Z 0-9, and underscores in "
- "between.",
- reply_markup=BotKeyboard.random_username(),
- )
- schedule_delete_message(call.message.chat.id, username_msg.id)
- await bot.set_state(
- call.from_user.id, botStates.add_user_username, call.message.chat.id
- )
- # bot.register_next_step_handler(username_msg, add_user_username_step)
-
-
-@bot.message_handler(state=botStates.add_user_username)
-async def add_user_username_step(message: types.Message):
- username = message.text
- if not username:
- wait_msg = await bot.send_message(
- message.chat.id, "β Username can not be empty."
- )
- schedule_delete_message(message.chat.id, wait_msg.id)
- schedule_delete_message(message.chat.id, message.id)
- return # bot.register_next_step_handler(wait_msg, add_user_username_step)
- if not re.match(r"^(?!.*__)(?!.*_$)\w{2,31}[a-zA-Z\d]$", username):
- wait_msg = await bot.send_message(
- message.chat.id,
- "β Username only can be 3 to 32 characters and contain a-z, A-Z, 0-9, and underscores in between.",
- )
- schedule_delete_message(message.chat.id, wait_msg.id)
- schedule_delete_message(message.chat.id, message.id)
- return # bot.register_next_step_handler(wait_msg, add_user_username_step)
- with GetDB() as db:
- if crud.get_user(db, username):
- wait_msg = await bot.send_message(
- message.chat.id, "β Username already exists."
- )
- schedule_delete_message(message.chat.id, wait_msg.id)
- schedule_delete_message(message.chat.id, message.id)
- return # bot.register_next_step_handler(wait_msg, add_user_username_step)
- schedule_delete_message(message.chat.id, message.id)
- await cleanup_messages(message.chat.id)
- msg = await bot.send_message(
- message.chat.id,
- "β¬οΈ Enter Data Limit (GB):\nβ οΈ Send 0 for unlimited.",
- reply_markup=BotKeyboard.inline_cancel_action(),
- )
- schedule_delete_message(message.chat.id, msg.id)
- await bot.set_state(
- message.from_user.id, botStates.add_user_data_limit, message.chat.id
- )
- async with bot.retrieve_data(message.from_user.id, message.chat.id) as data:
- data["username"] = username
- # bot.register_next_step_handler(msg, add_user_data_limit_step, username=username)
-
-
-@bot.message_handler(state=botStates.add_user_data_limit)
-async def add_user_data_limit_step(message: types.Message):
- try:
- if float(message.text) < 0:
- wait_msg = await bot.send_message(
- message.chat.id, "β Data limit must be greater or equal to 0."
- )
- schedule_delete_message(message.chat.id, wait_msg.id)
- schedule_delete_message(message.chat.id, message.id)
- return # bot.register_next_step_handler(wait_msg, add_user_data_limit_step, username=username)
- data_limit = float(message.text) * 1024 * 1024 * 1024
- except ValueError:
- wait_msg = await bot.send_message(
- message.chat.id, "β Data limit must be a number."
- )
- schedule_delete_message(message.chat.id, wait_msg.id)
- schedule_delete_message(message.chat.id, message.id)
- return # bot.register_next_step_handler(wait_msg, add_user_data_limit_step, username=username)
- schedule_delete_message(message.chat.id, message.id)
- await cleanup_messages(message.chat.id)
- msg = await bot.send_message(
- message.chat.id,
- "β¬οΈ Enter Expire Date (YYYY-MM-DD)\nOr You Can Use Regex Symbol: ^[0-9]{1,3}(M|D) :\nβ οΈ Send 0 for never expire.",
- reply_markup=BotKeyboard.inline_cancel_action(),
- )
- schedule_delete_message(message.chat.id, msg.id)
- await bot.set_state(
- message.from_user.id, botStates.add_user_expire, message.chat.id
- )
- async with bot.retrieve_data(message.from_user.id, message.chat.id) as data:
- data["data_limit"] = data_limit
-
- # bot.register_next_step_handler(msg, add_user_expire_step, username=username, data_limit=data_limit)
-
-
-@bot.message_handler(state=botStates.add_user_expire)
-async def add_user_expire_step(message: types.Message):
- try:
- now = datetime.now()
- today = datetime(
- year=now.year, month=now.month, day=now.day, hour=23, minute=59, second=59
- )
- if re.match(r"^[0-9]{1,3}(M|m|D|d)$", message.text):
- expire_date = today
- number_pattern = r"^[0-9]{1,3}"
- number = int(re.findall(number_pattern, message.text)[0])
- symbol_pattern = r"(M|m|D|d)$"
- symbol = re.findall(symbol_pattern, message.text)[0].upper()
- if symbol == "M":
- expire_date = today + relativedelta(months=number)
- elif symbol == "D":
- expire_date = today + relativedelta(days=number)
- elif message.text != "0":
- expire_date = datetime.strptime(message.text, "%Y-%m-%d")
- else:
- expire_date = None
- if expire_date and expire_date < today:
- wait_msg = await bot.send_message(
- message.chat.id, "β Expire date must be greater than today."
- )
- schedule_delete_message(message.chat.id, wait_msg.id)
- schedule_delete_message(message.chat.id, message.id)
- return # bot.register_next_step_handler(wait_msg, add_user_expire_step, username=username, data_limit=data_limit)
- except ValueError:
- 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)",
- )
- schedule_delete_message(message.chat.id, wait_msg.id)
- schedule_delete_message(message.chat.id, message.id)
- return # bot.register_next_step_handler(wait_msg, add_user_expire_step, username=username, data_limit=data_limit)
- async with bot.retrieve_data(message.from_user.id, message.chat.id) as data:
- username, data_limit = data["username"], data["data_limit"]
- mem_store.set(f"{message.chat.id}:username", username)
- mem_store.set(f"{message.chat.id}:data_limit", data_limit)
- mem_store.set(f"{message.chat.id}:expire_date", expire_date)
-
- with GetDB() as db:
- services = crud.get_services(db)
- schedule_delete_message(message.chat.id, message.id)
- await cleanup_messages(message.chat.id)
- await bot.send_message(
- message.chat.id,
- "Select Services:\nUsernames: {}\nData Limit: {}\nExpiry Date {}".format(
- mem_store.get(f"{message.chat.id}:username"),
- (
- readable_size(mem_store.get(f"{message.chat.id}:data_limit"))
- if mem_store.get(f"{message.chat.id}:data_limit")
- else "Unlimited"
- ),
- (
- mem_store.get(f"{message.chat.id}:expire_date").strftime("%Y-%m-%d")
- if mem_store.get(f"{message.chat.id}:expire_date")
- else "Never"
- ),
- ),
- reply_markup=BotKeyboard.select_services(services, [], action="create"),
- )
-
-
-@bot.callback_query_handler(query_startswith("switch_service:"), is_admin=True)
-async def switch_service(call: types.CallbackQuery):
- if not (username := mem_store.get(f"{call.message.chat.id}:username")):
- return await bot.answer_callback_query(
- call.id, "β No user selected.", show_alert=True
- )
- selected_services: List[int] = mem_store.get(f"{call.message.chat.id}:services", [])
- _, service, action = call.data.split(":")
- service = int(service)
-
- with GetDB() as db:
- services = crud.get_services(db)
- if service in selected_services:
- selected_services.remove(service)
- else:
- selected_services.append(service)
-
- mem_store.set(f"{call.message.chat.id}:services", selected_services)
-
- if action in ["edit"]:
- return await bot.edit_message_text(
- call.message.text,
- call.message.chat.id,
- call.message.message_id,
- reply_markup=BotKeyboard.select_services(
- services,
- selected_services,
- "edit",
- username=username,
- data_limit=mem_store.get(f"{call.message.chat.id}:data_limit"),
- expire_date=mem_store.get(f"{call.message.chat.id}:expire_date"),
- ),
- )
- await bot.edit_message_text(
- call.message.text,
- call.message.chat.id,
- call.message.message_id,
- reply_markup=BotKeyboard.select_services(services, selected_services, "create"),
- )
-
-
-@bot.callback_query_handler(query_startswith("confirm:"), is_admin=True)
-async def confirm_user_command(call: types.CallbackQuery):
- data = call.data.split(":")[1]
- chat_id = call.from_user.id
- full_name = call.from_user.full_name
- now = datetime.now()
- today = datetime(
- year=now.year, month=now.month, day=now.day, hour=23, minute=59, second=59
- )
-
-
-@bot.callback_query_handler(query_startswith("do_delete"), is_admin=True)
-async def delete_user(call: types.CallbackQuery):
- username = call.data.split(":")[1]
- with GetDB() as db:
- db_user = crud.get_user(db, username)
- crud.remove_user(db, db_user)
- await xray.operations.remove_user(db_user)
-
- await bot.edit_message_text(
- "β
User deleted.",
- call.message.chat.id,
- call.message.message_id,
- reply_markup=BotKeyboard.main_menu(),
- )
- if TELEGRAM_LOGGER_CHANNEL_ID:
- text = f"""\
-π #Deleted #From_Bot
-βββββββββ
-Username : {db_user.username}
-Traffic Limit : {readable_size(db_user.data_limit) if db_user.data_limit else "Unlimited"}
-Expire Date : \
-{datetime.fromtimestamp(db_user.expire).strftime('%H:%M:%S %Y-%m-%d') if db_user.expire else "Never"}
-βββββββββ
-By : {call.from_user.full_name}"""
- try:
- await bot.send_message(TELEGRAM_LOGGER_CHANNEL_ID, text, "HTML")
- except:
- pass
-
-
-@bot.callback_query_handler(query_startswith("do_suspend"), is_admin=True)
-async def disable_user(call: types.CallbackQuery):
- username = call.data.split(":")[1]
- with GetDB() as db:
- db_user = crud.get_user(db, username)
- crud.update_user(
- db, db_user, UserModify(username=username, status=UserStatusModify.disabled)
- )
- await xray.operations.remove_user(db_user)
- user = UserResponse.model_validate(db_user)
- await bot.edit_message_text(
- get_user_info_text(
- status="disabled",
- username=username,
- sub_url=user.subscription_url,
- data_limit=db_user.data_limit,
- usage=db_user.used_traffic,
- expire=db_user.expire,
- note=user.note,
- ),
- call.message.chat.id,
- call.message.message_id,
- parse_mode="HTML",
- reply_markup=BotKeyboard.user_menu(
- user_info={"status": "disabled", "username": db_user.username}, note=True
- ),
- )
- if TELEGRAM_LOGGER_CHANNEL_ID:
- text = f"""\
-β #Disabled #From_Bot
-βββββββββ
-Username : {username}
-βββββββββ
-By : {call.from_user.full_name}"""
- try:
- await bot.send_message(TELEGRAM_LOGGER_CHANNEL_ID, text, "HTML")
- except:
- pass
-
-
-@bot.callback_query_handler(query_startswith("do_activate"), is_admin=True)
-async def activate_user(call: types.CallbackQuery):
- username = call.data.split(":")[1]
- with GetDB() as db:
- db_user = crud.get_user(db, username)
- crud.update_user(
- db, db_user, UserModify(username=username, status=UserStatusModify.active)
- )
- await xray.operations.add_user(db_user)
- user = UserResponse.model_validate(db_user)
- await bot.edit_message_text(
- get_user_info_text(
- status="active",
- username=username,
- sub_url=user.subscription_url,
- data_limit=db_user.data_limit,
- usage=db_user.used_traffic,
- expire=db_user.expire,
- note=user.note,
- ),
- call.message.chat.id,
- call.message.message_id,
- parse_mode="HTML",
- reply_markup=BotKeyboard.user_menu(
- user_info={"status": "active", "username": db_user.username}, note=True
- ),
- )
- if TELEGRAM_LOGGER_CHANNEL_ID:
- text = f"""\
-β
#Activated #From_Bot
-βββββββββ
-Username : {username}
-βββββββββ
-By : {call.from_user.full_name}"""
- try:
- await bot.send_message(TELEGRAM_LOGGER_CHANNEL_ID, text, "HTML")
- except:
- pass
-
-
-@bot.callback_query_handler(query_startswith("do_reset_usage"), is_admin=True)
-async def reset_all_usages(call: types.CallbackQuery):
- username = call.data.split(":")[1]
- with GetDB() as db:
- db_user = crud.get_user(db, username)
- crud.reset_user_data_usage(db, db_user)
- user = UserResponse.model_validate(db_user)
- await bot.edit_message_text(
- get_user_info_text(
- status=user.status,
- username=username,
- sub_url=user.subscription_url,
- data_limit=user.data_limit,
- usage=user.used_traffic,
- expire=user.expire,
- note=user.note,
- ),
- call.message.chat.id,
- call.message.message_id,
- parse_mode="HTML",
- reply_markup=BotKeyboard.user_menu(
- user_info={"status": user.status, "username": user.username}, note=True
- ),
- )
- if TELEGRAM_LOGGER_CHANNEL_ID:
- text = f"""\
-π #Reset_usage #From_Bot
-βββββββββ
-Username : {username}
-βββββββββ
-By : {call.from_user.full_name}"""
- try:
- await bot.send_message(TELEGRAM_LOGGER_CHANNEL_ID, text, "HTML")
- except:
- pass
-
-
-@bot.callback_query_handler(query_equals("do_restart"), is_admin=True)
-async def restart_xray(call: types.CallbackQuery):
- m = await bot.edit_message_text(
- "π Restarting XRay core...", call.message.chat.id, call.message.message_id
- )
- await xray.core.restart(xray.config.include_db_users())
- for node_id, node in list(xray.nodes.items()):
- if await node.is_healthy():
- await xray.operations.restart_node(node_id, xray.config.include_db_users())
- await bot.edit_message_text(
- "β
XRay core restarted successfully.",
- m.chat.id,
- m.message_id,
- reply_markup=BotKeyboard.main_menu(),
- )
-
-
-@bot.callback_query_handler(query_equals("edit_user_done"), is_admin=True)
-async def edit_user(call: types.CallbackQuery):
- if (username := mem_store.get(f"{call.message.chat.id}:username")) is None:
- try:
- await bot.delete_message(call.message.chat.id, call.message.message_id)
- except Exception:
- pass
- return await bot.send_message(
- call.message.chat.id,
- "β Bot reload detected. Please start over.",
- reply_markup=BotKeyboard.main_menu(),
- )
-
- if not mem_store.get(f"{call.message.chat.id}:services"):
- return await bot.answer_callback_query(
- call.id, "β No Service selected.", show_alert=True
- )
-
- # inbounds: dict[str, list[str]] = {
- # k: v for k, v in mem_store.get(f'{call.message.chat.id}:protocols').items() if v}
- services: List[int] = mem_store.get(f"{call.message.chat.id}:services")
-
- with GetDB() as db:
- db_user = crud.get_user(db, username)
- if not db_user:
- return await bot.answer_callback_query(
- call.id, text=f"User not found!", show_alert=True
- )
-
- # proxies = {p.type.value: p.settings for p in db_user.proxies}
-
- """for protocol in xray.config.inbounds_by_protocol:
- if protocol in inbounds and protocol not in db_user.inbounds:
- proxies.update({protocol: {'flow': TELEGRAM_DEFAULT_VLESS_FLOW} if
- TELEGRAM_DEFAULT_VLESS_FLOW and protocol == ProxyTypes.VLESS else {}})
- elif protocol in db_user.inbounds and protocol not in inbounds:
- del proxies[protocol]"""
-
- modify = UserModify(
- username=username,
- # status=db_user.status,
- expire=(
- int(mem_store.get(f"{call.message.chat.id}:expire_date").timestamp())
- if mem_store.get(f"{call.message.chat.id}:expire_date")
- else 0
- ),
- data_limit=mem_store.get(f"{call.message.chat.id}:data_limit"),
- services=services,
- )
- last_user = UserResponse.model_validate(db_user)
- db_user = crud.update_user(db, db_user, modify)
-
- user = UserResponse.model_validate(db_user)
-
- if user.status == UserStatus.active:
- await xray.operations.update_user(db_user)
- else:
- await xray.operations.remove_user(db_user)
-
- await bot.answer_callback_query(call.id, "β
User updated successfully.")
-
- text = get_user_info_text(
- status=user.status,
- username=user.username,
- sub_url=user.subscription_url,
- data_limit=user.data_limit,
- usage=user.used_traffic,
- expire=user.expire,
- note=user.note,
- )
- await bot.edit_message_text(
- text,
- call.message.chat.id,
- call.message.message_id,
- parse_mode="HTML",
- reply_markup=BotKeyboard.user_menu(
- {"username": db_user.username, "status": db_user.status}, note=True
- ),
- )
- if TELEGRAM_LOGGER_CHANNEL_ID:
- tag = f'\nβββββββββ \nBy : {call.from_user.full_name}'
- if last_user.data_limit != user.data_limit:
- text = f"""\
-πΆ #Traffic_Change #From_Bot
-βββββββββ
-Username : {user.username}
-Last Traffic Limit : {readable_size(last_user.data_limit) if last_user.data_limit else "Unlimited"}
-New Traffic Limit : {readable_size(user.data_limit) if user.data_limit else "Unlimited"}{tag}"""
- try:
- await bot.send_message(TELEGRAM_LOGGER_CHANNEL_ID, text, "HTML")
- except:
- pass
- if last_user.expire != user.expire:
- text = f"""\
-π
#Expiry_Change #From_Bot
-βββββββββ
-Username : {user.username}
-Last Expire Date : \
-{datetime.fromtimestamp(last_user.expire).strftime('%H:%M:%S %Y-%m-%d') if last_user.expire else "Never"}
-New Expire Date : \
-{datetime.fromtimestamp(user.expire).strftime('%H:%M:%S %Y-%m-%d') if user.expire else "Never"}{tag}"""
- try:
- await bot.send_message(TELEGRAM_LOGGER_CHANNEL_ID, text, "HTML")
- except:
- pass
- if list(last_user.inbounds.values())[0] != list(user.inbounds.values())[0]:
- text = f"""\
-βοΈ #Inbounds_Change #From_Bot
-βββββββββ
-Username : {user.username}
-Last Proxies : {", ".join(list(last_user.inbounds.values())[0])}
-New Proxies : {", ".join(list(user.inbounds.values())[0])}{tag}"""
- try:
- await bot.send_message(TELEGRAM_LOGGER_CHANNEL_ID, text, "HTML")
- except:
- pass
-
-
-@bot.callback_query_handler(query_startswith("add_user_done"), is_admin=True)
-async def delete_limpired(call: types.CallbackQuery):
- if mem_store.get(f"{call.message.chat.id}:username") is None:
- try:
- await bot.delete_message(call.message.chat.id, call.message.message_id)
- except Exception:
- pass
- return await bot.send_message(
- call.message.chat.id,
- "β Bot reload detected. Please start over.",
- reply_markup=BotKeyboard.main_menu(),
- )
-
- if not mem_store.get(f"{call.message.chat.id}:services"):
- return await bot.answer_callback_query(
- call.id, "β No services selected.", show_alert=True
- )
-
- services: List[int] = mem_store.get(f"{call.message.chat.id}:services", [])
-
- new_user = UserCreate(
- username=mem_store.get(f"{call.message.chat.id}:username"),
- expire=(
- int(mem_store.get(f"{call.message.chat.id}:expire_date").timestamp())
- if mem_store.get(f"{call.message.chat.id}:expire_date")
- else None
- ),
- data_limit=(
- mem_store.get(f"{call.message.chat.id}:data_limit")
- if mem_store.get(f"{call.message.chat.id}:data_limit")
- else None
- ),
- services=services,
- )
- try:
- with GetDB() as db:
- db_user = crud.create_user(db, new_user)
- services = db_user.services
- user = UserResponse.model_validate(db_user)
- except sqlalchemy.exc.IntegrityError:
- db.rollback()
- return await bot.answer_callback_query(
- call.id, "β Username already exists.", show_alert=True
- )
-
- await xray.operations.add_user(db_user)
-
- text = get_user_info_text(
- status=user.status,
- username=user.username,
- sub_url=user.subscription_url,
- data_limit=user.data_limit,
- usage=user.used_traffic,
- expire=user.expire,
- note=user.note,
- )
- await bot.edit_message_text(
- text,
- call.message.chat.id,
- call.message.message_id,
- parse_mode="HTML",
- reply_markup=BotKeyboard.user_menu(
- user_info={"status": user.status, "username": user.username}, note=True
- ),
- )
-
- if TELEGRAM_LOGGER_CHANNEL_ID:
- text = f"""\
-π #Created #From_Bot
-βββββββββ
-Username : {user.username}
-Traffic Limit : {readable_size(user.data_limit) if user.data_limit else "Unlimited"}
-Expire Date : \
-{datetime.fromtimestamp(user.expire).strftime('%H:%M:%S %Y-%m-%d') if user.expire else "Never"}
-Services : {"" if not services else ", ".join([s.name for s in services])}
-βββββββββ
-By : {call.from_user.full_name}"""
- try:
- await bot.send_message(TELEGRAM_LOGGER_CHANNEL_ID, text, "HTML")
- except:
- pass
-
-
-@bot.callback_query_handler(
- query_startswith(["do_delete_expired", "do_delete_limited"]), is_admin=True
-)
-async def delete_limpired(call: types.CallbackQuery):
- await bot.edit_message_text(
- "β³ In Progress...",
- call.message.chat.id,
- call.message.message_id,
- parse_mode="HTML",
- )
- with GetDB() as db:
- depleted_users = crud.get_users(
- db,
- status=[
- UserStatus.limited if data == "delete_limited" else UserStatus.expired
- ],
- )
- file_name = f"{data[8:]}_users_{int(now.timestamp()*1000)}.txt"
- with open(file_name, "w") as f:
- f.write("USERNAME\tEXIPRY\tUSAGE/LIMIT\tSTATUS\n")
- deleted = 0
- for user in depleted_users:
- try:
- crud.remove_user(db, user)
- await xray.operations.remove_user(user)
- deleted += 1
- f.write(
- f'{user.username}\
-\t{datetime.fromtimestamp(user.expire) if user.expire else "never"}\
-\t{readable_size(user.used_traffic) if user.used_traffic else 0}\
-/{readable_size(user.data_limit) if user.data_limit else "Unlimited"}\
-\t{user.status}\n'
- )
- except:
- db.rollback()
- await bot.edit_message_text(
- f"β
{deleted}/{len(depleted_users)} {data[7:].title()} Users Deleted",
- call.message.chat.id,
- call.message.message_id,
- parse_mode="HTML",
- reply_markup=BotKeyboard.main_menu(),
- )
- if TELEGRAM_LOGGER_CHANNEL_ID:
- text = f"""\
-π #Delete #{data[7:].title()} #From_Bot
-βββββββββ
-Count: {deleted}
-βββββββββ
-By : {call.from_user.full_name}"""
- try:
- await bot.send_document(
- TELEGRAM_LOGGER_CHANNEL_ID,
- open(file_name, "rb"),
- caption=text,
- parse_mode="HTML",
- )
- os.remove(file_name)
- except:
- pass
-
-
-@bot.callback_query_handler(query_startswith("do_add_data:"), is_admin=True)
-async def add_data_to_all(call: types.CallbackQuery):
- schedule_delete_message(
- call.message.chat.id,
- (await bot.send_message(chat_id, "β³ In Progress...", "HTML")).id,
- )
- data_limit = float(call.data.split(":")[1]) * 1024 * 1024 * 1024
- with GetDB() as db:
- users = crud.get_users(db)
- counter = 0
- file_name = f"new_data_limit_users_{int(now.timestamp()*1000)}.txt"
- with open(file_name, "w") as f:
- f.write("USERNAME\tEXIPRY\tUSAGE/LIMIT\tSTATUS\n")
- for user in users:
- try:
- if user.data_limit and user.status not in [
- UserStatus.limited,
- UserStatus.expired,
- ]:
- user = crud.update_user(
- db,
- user,
- UserModify(
- username=user.username,
- data_limit=(user.data_limit + data_limit),
- ),
- )
- counter += 1
- f.write(
- f'{user.username}\
-\t{datetime.fromtimestamp(user.expire) if user.expire else "never"}\
-\t{readable_size(user.used_traffic) if user.used_traffic else 0}\
-/{readable_size(user.data_limit) if user.data_limit else "Unlimited"}\
-\t{user.status}\n'
- )
- except:
- db.rollback()
- await cleanup_messages(chat_id)
- await bot.send_message(
- chat_id,
- f'β
{counter}/{len(users)} Users Data Limit according to {"+" if data_limit > 0 else "-"}{readable_size(abs(data_limit))}',
- "HTML",
- reply_markup=BotKeyboard.main_menu(),
- )
- if TELEGRAM_LOGGER_CHANNEL_ID:
- text = f"""\
-πΆ #Traffic_Change #From_Bot
-βββββββββ
-According to: {"+" if data_limit > 0 else "-"}{readable_size(abs(data_limit))}
-Count: {counter}
-βββββββββ
-By : {call.from_user.full_name}"""
- try:
- await bot.send_document(
- TELEGRAM_LOGGER_CHANNEL_ID,
- open(file_name, "rb"),
- caption=text,
- parse_mode="HTML",
- )
- os.remove(file_name)
- except:
- pass
-
-
-@bot.callback_query_handler(query_startswith("do_add_time:"), is_admin=True)
-async def add_time_to_all(call: types.CallbackQuery):
- schedule_delete_message(
- call.message.chat.id,
- (await bot.send_message(chat_id, "β³ In Progress...", "HTML")).id,
- )
- days = int(call.data.split(":")[1])
- with GetDB() as db:
- users = crud.get_users(db)
- counter = 0
- file_name = f"new_expiry_users_{int(now.timestamp()*1000)}.txt"
- with open(file_name, "w") as f:
- f.write("USERNAME\tEXIPRY\tUSAGE/LIMIT\tSTATUS\n")
- for user in users:
- try:
- if user.expire and user.status not in [
- UserStatus.limited,
- UserStatus.expired,
- ]:
- user = crud.update_user(
- db,
- user,
- UserModify(
- username=user.username,
- expire=int(
- (
- datetime.fromtimestamp(user.expire)
- + relativedelta(days=days)
- ).timestamp()
- ),
- ),
- )
- counter += 1
- f.write(
- f'{user.username}\
-\t{datetime.fromtimestamp(user.expire) if user.expire else "never"}\
-\t{readable_size(user.used_traffic) if user.used_traffic else 0}\
-/{readable_size(user.data_limit) if user.data_limit else "Unlimited"}\
-\t{user.status}\n'
- )
- except:
- db.rollback()
- await cleanup_messages(chat_id)
- await bot.send_message(
- chat_id,
- f"β
{counter}/{len(users)} Users Expiry Changes according to {days} Days",
- "HTML",
- reply_markup=BotKeyboard.main_menu(),
- )
- if TELEGRAM_LOGGER_CHANNEL_ID:
- text = f"""\
-π
#Expiry_Change #From_Bot
-βββββββββ
-According to: {days} Days
-Count: {counter}
-βββββββββ
-By : {call.from_user.full_name}"""
- try:
- await bot.send_document(
- TELEGRAM_LOGGER_CHANNEL_ID,
- open(file_name, "rb"),
- caption=text,
- parse_mode="HTML",
- )
- os.remove(file_name)
- except:
- pass
-
-
-@bot.callback_query_handler(query_startswith("do_revoke:"), is_admin=True)
-async def revoke_sub(call: types.CallbackQuery):
- username = call.data.split(":")[1]
- with GetDB() as db:
- db_user = crud.get_user(db, username)
- if not db_user:
- return await bot.answer_callback_query(
- call.id, text=f"User not found!", show_alert=True
- )
- db_user = crud.revoke_user_sub(db, db_user)
- user = UserResponse.model_validate(db_user)
- text = get_user_info_text(
- status=user.status,
- username=user.username,
- sub_url=user.subscription_url,
- expire=user.expire,
- data_limit=user.data_limit,
- usage=user.used_traffic,
- note=user.note,
- )
- await bot.edit_message_text(
- f"β
Subscription Successfully Revoked!\n\n{text}",
- call.message.chat.id,
- call.message.message_id,
- parse_mode="HTML",
- reply_markup=BotKeyboard.user_menu(
- user_info={"status": user.status, "username": user.username}, note=True
- ),
- )
-
- if TELEGRAM_LOGGER_CHANNEL_ID:
- text = f"""\
-π« #Revoke_sub #From_Bot
-βββββββββ
-Username: {username}
-βββββββββ
-By : {call.from_user.full_name}"""
- try:
- await bot.send_message(TELEGRAM_LOGGER_CHANNEL_ID, text, "HTML")
- except:
- pass
-
-
-@bot.message_handler(func=lambda message: True, is_admin=True)
-async def search(message: types.Message):
- with GetDB() as db:
- db_user = crud.get_user(db, message.text)
- if not db_user:
- return await bot.reply_to(message, "β User not found.")
- user = UserResponse.model_validate(db_user)
- text = get_user_info_text(
- status=user.status,
- username=user.username,
- sub_url=user.subscription_url,
- expire=user.expire,
- data_limit=user.data_limit,
- usage=user.used_traffic,
- note=user.note,
- )
- return await bot.reply_to(
- message,
- text,
- parse_mode="html",
- reply_markup=BotKeyboard.user_menu(
- user_info={"status": user.status, "username": user.username}, note=True
- ),
- )
diff --git a/app/telegram/handlers/user.py b/app/telegram/handlers/user.py
deleted file mode 100644
index 696b2e9..0000000
--- a/app/telegram/handlers/user.py
+++ /dev/null
@@ -1,42 +0,0 @@
-from datetime import datetime
-from app.db import GetDB, crud
-from app.models.user import UserResponse
-from app.telegram import bot
-
-# from pytz import UTC
-from telebot.custom_filters import ChatFilter
-from telebot.util import extract_arguments
-
-from app.utils.system import readable_size
-
-bot.add_custom_filter(ChatFilter())
-
-
-@bot.message_handler(commands=["usage"])
-def usage_command(message):
- username = extract_arguments(message.text)
- if not username:
- return bot.reply_to(
- message, "Usage: `/usage `", parse_mode="MarkdownV2"
- )
-
- with GetDB() as db:
- dbuser = crud.get_user(db, username)
-
- if not dbuser:
- return bot.reply_to(message, "No user found with this username")
- user = UserResponse.model_validate(dbuser)
-
- statuses = {"active": "β
", "expired": "π°", "limited": "π΅", "disabled": "β"}
-
- text = f"""\
-ββ{statuses[user.status]} Status: {user.status.title()}
-β ββUsername: {user.username}
-β
-ββπ Data limit: {readable_size(user.data_limit) if user.data_limit else 'Unlimited'}
-β ββData Used: {readable_size(user.used_traffic) if user.used_traffic else "-"}
-β
-ββπ
Expiry Date: {datetime.fromtimestamp(user.expire).date() if user.expire else 'Never'}
- ββDays left: {(datetime.fromtimestamp(user.expire or 0) - datetime.now()).days if user.expire else '-'}"""
-
- return bot.reply_to(message, text, parse_mode="HTML")
diff --git a/app/telegram/handlers/report.py b/app/telegram/report.py
similarity index 85%
rename from app/telegram/handlers/report.py
rename to app/telegram/report.py
index bb1ab51..8a73630 100644
--- a/app/telegram/handlers/report.py
+++ b/app/telegram/report.py
@@ -4,8 +4,7 @@ from datetime import datetime
from telebot.apihelper import ApiTelegramException
from telebot.formatting import escape_html
-from app.telegram import bot
-from app.telegram.utils.keyboard import BotKeyboard
+from app import telegram
from app.utils.system import readable_size
from config import TELEGRAM_ADMIN_ID, TELEGRAM_LOGGER_CHANNEL_ID
@@ -13,15 +12,15 @@ logger = logging.getLogger(__name__)
async def report(message: str, parse_mode="html", keyboard=None):
- if bot and (TELEGRAM_ADMIN_ID or TELEGRAM_LOGGER_CHANNEL_ID):
+ if telegram.bot and (TELEGRAM_ADMIN_ID or TELEGRAM_LOGGER_CHANNEL_ID):
try:
if TELEGRAM_LOGGER_CHANNEL_ID:
- await bot.send_message(
+ await telegram.bot.send_message(
TELEGRAM_LOGGER_CHANNEL_ID, message, parse_mode=parse_mode
)
else:
for admin in TELEGRAM_ADMIN_ID:
- await bot.send_message(
+ await telegram.bot.send_message(
admin, message, parse_mode=parse_mode, reply_markup=keyboard
)
except ApiTelegramException as e:
@@ -54,12 +53,7 @@ async def report_new_user(
services="" if not services else ", ".join([s.name for s in services]),
)
- return await report(
- text,
- keyboard=BotKeyboard.user_menu(
- {"username": username, "id": user_id, "status": "active"}, with_back=False
- ),
- )
+ return await report(text)
async def report_user_modification(
@@ -84,12 +78,7 @@ async def report_user_modification(
services=", ".join([s.name for s in services]),
)
- return await report(
- text,
- keyboard=BotKeyboard.user_menu(
- {"username": username, "status": "active"}, with_back=False
- ),
- )
+ return await report(text)
async def report_user_deletion(username: str, by: str):
@@ -108,9 +97,8 @@ async def report_user_deletion(username: str, by: str):
async def report_status_change(username: str, status: str):
_status = {
"active": "β
#Activated",
- "disabled": "β #Disabled",
- "limited": "πͺ« #Limited",
"on_hold": "π‘ #On_Hold",
+ "limited": "πͺ« #Limited",
"expired": "π #Expired",
}
text = """\
diff --git a/app/telegram/utils/__init__.py b/app/telegram/utils/__init__.py
deleted file mode 100644
index 6699e5e..0000000
--- a/app/telegram/utils/__init__.py
+++ /dev/null
@@ -1,5 +0,0 @@
-from app.telegram.utils import custom_filters
-
-
-def setup() -> None:
- custom_filters.setup()
diff --git a/app/telegram/utils/custom_filters.py b/app/telegram/utils/custom_filters.py
deleted file mode 100644
index 9d0fc82..0000000
--- a/app/telegram/utils/custom_filters.py
+++ /dev/null
@@ -1,35 +0,0 @@
-from app.telegram import bot
-from typing import Union
-
-from telebot import types
-from telebot.asyncio_filters import AdvancedCustomFilter, StateFilter
-
-from config import TELEGRAM_ADMIN_ID
-
-
-class IsAdminFilter(AdvancedCustomFilter):
- key = "is_admin"
-
- async def check(self, message, text):
- """
- :meta private:
- """
- if isinstance(message, types.CallbackQuery):
- return message.from_user.id in TELEGRAM_ADMIN_ID
- return message.chat.id in TELEGRAM_ADMIN_ID
-
-
-def query_equals(text: str):
- return lambda query: query.data == text
-
-
-def query_startswith(text: Union[list, str]):
- if type(text) is str:
- return lambda query: query.data.startswith(text)
- else:
- return lambda query: query.data.startswith(tuple(text))
-
-
-def setup() -> None:
- bot.add_custom_filter(IsAdminFilter())
- bot.add_custom_filter(StateFilter(bot))
diff --git a/app/telegram/utils/keyboard.py b/app/telegram/utils/keyboard.py
deleted file mode 100644
index 421f990..0000000
--- a/app/telegram/utils/keyboard.py
+++ /dev/null
@@ -1,286 +0,0 @@
-from datetime import datetime as dt
-from itertools import islice
-from typing import Literal, Dict, List
-
-from telebot import types # noqa
-
-from app.utils.system import readable_size
-
-
-def chunk_dict(data: dict, size: int = 2):
- it = iter(data)
- for i in range(0, len(data), size):
- yield {k: data[k] for k in islice(it, size)}
-
-
-class BotKeyboard:
-
- @staticmethod
- def main_menu():
- keyboard = types.InlineKeyboardMarkup()
- keyboard.add(
- types.InlineKeyboardButton(text="π System Info", callback_data="system"),
- types.InlineKeyboardButton(text="β»οΈ Restart Xray", callback_data="restart"),
- )
- keyboard.add(
- types.InlineKeyboardButton(text="π₯ Users", callback_data="users:1"),
- types.InlineKeyboardButton(
- text="βοΈ Edit All Users", callback_data="edit_all"
- ),
- )
- # keyboard.add(
- # types.InlineKeyboardButton(text='β Create User From Template', callback_data='template_add_user'))
- keyboard.add(
- types.InlineKeyboardButton(text="β Create User", callback_data="add_user")
- )
- return keyboard
-
- @staticmethod
- def edit_all_menu():
- keyboard = types.InlineKeyboardMarkup()
- keyboard.add(
- types.InlineKeyboardButton(
- text="π Delete Expired", callback_data="delete_expired"
- ),
- types.InlineKeyboardButton(
- text="π Delete Limited", callback_data="delete_limited"
- ),
- )
- keyboard.add(
- types.InlineKeyboardButton(
- text="π Data (β|β)", callback_data="add_data"
- ),
- types.InlineKeyboardButton(
- text="π
Time (β|β)", callback_data="add_time"
- ),
- )
- # keyboard.add(
- # types.InlineKeyboardButton(text='β Add Inbound', callback_data='inbound_add'),
- # types.InlineKeyboardButton(text='β Remove Inbound', callback_data='inbound_remove'))
- keyboard.add(types.InlineKeyboardButton(text="π Back", callback_data="cancel"))
- return keyboard
-
- # @staticmethod
- # def inbounds_menu(action, inbounds):
- # keyboard = types.InlineKeyboardMarkup()
- # for inbound in inbounds:
- # keyboard.add(types.InlineKeyboardButton(text=inbound, callback_data=f'confirm_{action}:{inbound}'))
- # keyboard.add(types.InlineKeyboardButton(text='π Back', callback_data='cancel'))
- # return keyboard
-
- @staticmethod
- def random_username(template_id: str = ""):
- keyboard = types.InlineKeyboardMarkup()
-
- keyboard.add(
- types.InlineKeyboardButton(
- text="π‘ Random Username", callback_data=f"random:{template_id}"
- )
- )
- keyboard.add(
- types.InlineKeyboardButton(text="π Cancel", callback_data="cancel")
- )
- return keyboard
-
- @staticmethod
- def user_menu(user_info, with_back: bool = True, page: int = 1, note: bool = False):
- keyboard = types.InlineKeyboardMarkup()
- keyboard.add(
- types.InlineKeyboardButton(
- text="β Disable" if user_info["status"] == "active" else "β
Activate",
- callback_data=f"{'suspend' if user_info['status'] == 'active' else 'activate'}:{user_info['username']}",
- ),
- types.InlineKeyboardButton(
- text="π Delete", callback_data=f"delete:{user_info['username']}"
- ),
- )
- if note:
- keyboard.add(
- types.InlineKeyboardButton(
- text="π« Revoke Sub",
- callback_data=f"revoke_sub:{user_info['username']}",
- ),
- types.InlineKeyboardButton(
- text="βοΈ Edit", callback_data=f"edit:{user_info['username']}"
- ),
- )
- keyboard.add(
- types.InlineKeyboardButton(
- text="π Edit Note",
- callback_data=f"edit_note:{user_info['username']}",
- ),
- types.InlineKeyboardButton(
- text="π‘ Links", callback_data=f"links:{user_info['username']}"
- ),
- )
- else:
- keyboard.add(
- types.InlineKeyboardButton(
- text="π‘ Links", callback_data=f"links:{user_info['username']}"
- ),
- types.InlineKeyboardButton(
- text="βοΈ Edit", callback_data=f"edit:{user_info['username']}"
- ),
- )
- keyboard.add(
- types.InlineKeyboardButton(
- text="π Reset usage",
- callback_data=f"reset_usage:{user_info['username']}",
- ),
- types.InlineKeyboardButton(
- text="π Charge", callback_data=f"charge:{user_info['username']}"
- ),
- )
- if with_back:
- keyboard.add(
- types.InlineKeyboardButton(
- text="π Back", callback_data=f"users:{page}"
- )
- )
- return keyboard
-
- @staticmethod
- def show_links(username: str):
- keyboard = types.InlineKeyboardMarkup()
-
- keyboard.add(
- types.InlineKeyboardButton(
- text="πΌ QR code", callback_data=f"genqr:{username}"
- )
- )
- keyboard.add(
- types.InlineKeyboardButton(text="π Back", callback_data=f"user:{username}")
- )
- return keyboard
-
- @staticmethod
- def subscription_page(sub_url: str):
- keyboard = types.InlineKeyboardMarkup()
- if sub_url[:4] == "http":
- keyboard.add(
- types.InlineKeyboardButton(text="π Subscription Page", url=sub_url)
- )
- return keyboard
-
- @staticmethod
- def confirm_action(action: str, username: str = None):
- keyboard = types.InlineKeyboardMarkup()
- keyboard.add(
- types.InlineKeyboardButton(
- text="Yes", callback_data=f"{action}:{username}"
- ),
- types.InlineKeyboardButton(text="No", callback_data=f"cancel"),
- )
- return keyboard
-
- @staticmethod
- def inline_cancel_action(callback_data: str = "cancel"):
- keyboard = types.InlineKeyboardMarkup()
- keyboard.add(
- types.InlineKeyboardButton(text="π Cancel", callback_data=callback_data)
- )
- return keyboard
-
- @staticmethod
- def user_list(users: list, page: int, total_pages: int):
- keyboard = types.InlineKeyboardMarkup()
- if len(users) >= 2:
- users = [p for p in users]
- users = [users[i : i + 2] for i in range(0, len(users), 2)]
- else:
- users = [users]
- for user in users:
- row = []
- for p in user:
- status = {
- "active": "β
",
- "expired": "π°",
- "limited": "π΅",
- "disabled": "β",
- }
- row.append(
- types.InlineKeyboardButton(
- text=f"{p.username} ({status[p.status]})",
- callback_data=f"user:{p.username}:{page}",
- )
- )
- keyboard.row(*row)
- # if there is more than one page
- if total_pages > 1:
- if page > 1:
- keyboard.add(
- types.InlineKeyboardButton(
- text="β¬
οΈ Previous", callback_data=f"users:{page - 1}"
- )
- )
- if page < total_pages:
- keyboard.add(
- types.InlineKeyboardButton(
- text="β‘οΈ Next", callback_data=f"users:{page + 1}"
- )
- )
- keyboard.add(types.InlineKeyboardButton(text="π Back", callback_data="cancel"))
- return keyboard
-
- @staticmethod
- def select_services(
- services,
- selected_services: List[int],
- action: Literal["edit", "create"],
- username: str = None,
- data_limit: float = None,
- expire_date: dt = None,
- ):
-
- keyboard = types.InlineKeyboardMarkup()
-
- if action == "edit":
- keyboard.add(
- types.InlineKeyboardButton(
- text="β οΈ Data Limit:", callback_data=f"help_edit"
- )
- )
- keyboard.add(
- types.InlineKeyboardButton(
- text=f"{readable_size(data_limit) if data_limit else 'Unlimited'}",
- callback_data=f"help_edit",
- ),
- types.InlineKeyboardButton(
- text="βοΈ Edit", callback_data=f"edit_user:{username}:data"
- ),
- )
- keyboard.add(
- types.InlineKeyboardButton(
- text="π
Expire Date:", callback_data=f"help_edit"
- )
- )
- keyboard.add(
- types.InlineKeyboardButton(
- text=f"{expire_date.strftime('%Y-%m-%d') if expire_date else 'Never'}",
- callback_data=f"help_edit",
- ),
- types.InlineKeyboardButton(
- text="βοΈ Edit", callback_data=f"edit_user:{username}:expire"
- ),
- )
- for s in services:
- keyboard.add(
- types.InlineKeyboardButton(
- text=f"π {s.name} {'β
' if s.id in selected_services else 'β'}",
- callback_data=f"switch_service:{s.id}:{action}",
- )
- )
- keyboard.add(
- types.InlineKeyboardButton(
- text="Done",
- callback_data="edit_user_done" if action == "edit" else "add_user_done",
- )
- )
- keyboard.add(
- types.InlineKeyboardButton(
- text="Cancel",
- callback_data=f"user:{username}" if action == "edit" else "cancel",
- )
- )
-
- return keyboard