diff --git a/.env.example b/.env.example index 18f43b8..031d246 100644 --- a/.env.example +++ b/.env.example @@ -1,6 +1,6 @@ TELEGRAM_BOT_TOKEN='123456789:XXXXXXXXXXXXXXXXXXXXXXXXXXXX' -TELEGRAM_ADMINS_ID='123456789,987654321' +TELEGRAM_ADMINS_ID=[123456789 , 987654321] MARZBAN_ADDRESS='https://sub.domain.com:port' MARZBAN_USERNAME='sudo_username' MARZBAN_PASSWORD='sudo_password' -EXCLUDED_MONITORINGS='NODE_NAME_ONE,NODE_NAME_TWO' \ No newline at end of file +EXCLUDED_MONITORINGS=[NODE_NAME_ONE , NODE_NAME_TWO] \ No newline at end of file diff --git a/jobs/node_monitoring.py b/jobs/node_monitoring.py index 44292fa..88ab577 100644 --- a/jobs/node_monitoring.py +++ b/jobs/node_monitoring.py @@ -8,10 +8,9 @@ from db.crud import SettingManager, TokenManager from models.setting import SettingKeys -from utils import report -from utils.config import MARZBAN_ADDRESS, EXCLUDED_MONITORINGS +from utils import report, EnvSettings -panel = MarzbanAPI(base_url=MARZBAN_ADDRESS) +panel = MarzbanAPI(base_url=EnvSettings.MARZBAN_ADDRESS) async def node_checker(): @@ -29,7 +28,7 @@ async def node_checker(): nodes = await panel.get_nodes(token.token) anti_spam = False for node in nodes: - if node.name in EXCLUDED_MONITORINGS: + if node.name in EnvSettings.EXCLUDED_MONITORINGS: continue if node.status in ["connecting", "error"]: @@ -47,7 +46,7 @@ async def node_checker(): try: await panel.reconnect_node(node.id, token.token) await report.node_restart(node, True) - except (ConnectionError, TimeoutError): # Omit the variable if not used + except (ConnectionError, TimeoutError): await report.node_restart(node, False) if anti_spam: diff --git a/jobs/scheduler.py b/jobs/scheduler.py index 3cafe43..115976a 100644 --- a/jobs/scheduler.py +++ b/jobs/scheduler.py @@ -6,7 +6,7 @@ from apscheduler.triggers.interval import IntervalTrigger from jobs.token_updater import token_update from jobs.node_monitoring import node_checker -from utils.log import logger +from utils import logger scheduler = AsyncIOScheduler() diff --git a/jobs/token_updater.py b/jobs/token_updater.py index f996c7a..690d847 100644 --- a/jobs/token_updater.py +++ b/jobs/token_updater.py @@ -5,23 +5,22 @@ import httpx from marzban import MarzbanAPI -from utils.config import MARZBAN_PASSWORD, MARZBAN_USERNAME, MARZBAN_ADDRESS -from utils.log import logger +from utils import EnvSettings, logger from db.crud import TokenManager from models import TokenUpsert async def token_update() -> bool: """Add or update Marzban panel token every X time.""" - if not MARZBAN_USERNAME or not MARZBAN_PASSWORD: + if not EnvSettings.MARZBAN_USERNAME or not EnvSettings.MARZBAN_PASSWORD: logger.error("MARZBAN_USERNAME or MARZBAN_PASSWORD is not set.") return False - api = MarzbanAPI(base_url=MARZBAN_ADDRESS) + api = MarzbanAPI(base_url=EnvSettings.MARZBAN_ADDRESS) try: get_token = await api.get_token( - username=MARZBAN_USERNAME, password=MARZBAN_PASSWORD + username=EnvSettings.MARZBAN_USERNAME, password=EnvSettings.MARZBAN_PASSWORD ) if get_token and get_token.access_token: diff --git a/main.py b/main.py index de2c9bf..6c7dc5b 100644 --- a/main.py +++ b/main.py @@ -12,39 +12,40 @@ from jobs import stop_scheduler, start_scheduler from middlewares.auth import CheckAdminAccess from routers import setup_routers -from utils.config import TELEGRAM_BOT_TOKEN -from utils.log import logger -from utils.statedb import storage +from utils import EnvSettings, logger, storage async def on_startup() -> None: """Function to execute during bot startup.""" - logger.info("Bot is starting up...") + logger.info("HolderBot is starting up...") - logger.info("Starting scheduler...") + logger.info( + "Admin IDs: %s", ", ".join(map(str, EnvSettings.TELEGRAM_ADMINS_ID)) + ) # Log admin IDs + logger.info("Starting scheduler for HolderBot...") run_job = await start_scheduler() # Start scheduled tasks if not run_job: raise SystemExit( - logger.critical("Stopping the bot due to scheduler startup failure.") + logger.critical("Stopping HolderBot due to scheduler startup failure.") ) # Stop the bot if scheduler fails - logger.info("Scheduler is running.") - logger.info("Bot is up and running!") + logger.info("Scheduler is running for HolderBot.") + logger.info("HolderBot is up and running!") async def on_shutdown() -> None: """Function to execute during bot shutdown.""" - logger.info("Bot is shutting down...") + logger.info("HolderBot is shutting down...") logger.info("Stopping scheduler...") await stop_scheduler() # Stop scheduled tasks logger.info("Scheduler has stopped.") - logger.info("Bot has shut down successfully.") + logger.info("HolderBot has shut down successfully.") async def main() -> None: """Function to run aiogram bot.""" bot = Bot( - token=TELEGRAM_BOT_TOKEN, + token=EnvSettings.TELEGRAM_BOT_TOKEN, default=DefaultBotProperties( parse_mode=ParseMode.HTML, link_preview_is_disabled=True ), @@ -65,18 +66,21 @@ async def main() -> None: # Start polling the bot try: + bot_info = await bot.get_me() + logger.info("HolderBot [@%s] is starting to poll messages...", bot_info.username) await dp.start_polling(bot) except (ConnectionError, TimeoutError) as conn_err: logger.error("A connection error occurred while polling: %s", conn_err) except Exception as e: # pylint: disable=broad-except - logger.error("An error occurred while polling: %s", e) + logger.error("An error occurred while polling HolderBot: %s", e) if __name__ == "__main__": try: # Run the main function using asyncio + logger.info("Running HolderBot...") asyncio.run(main()) except KeyboardInterrupt: - logger.warning("Bot stopped by user.") + logger.warning("HolderBot stopped by user.") except Exception as e: # pylint: disable=broad-except - logger.error("An unexpected error occurred: %s", e) + logger.error("An unexpected error occurred in HolderBot: %s", e) diff --git a/middlewares/auth.py b/middlewares/auth.py index 2db27a4..cdfde15 100644 --- a/middlewares/auth.py +++ b/middlewares/auth.py @@ -6,7 +6,7 @@ from aiogram import BaseMiddleware from aiogram.types import Update -from utils import logger, storage, config +from utils import logger, storage, EnvSettings # pylint: disable=too-few-public-methods @@ -38,7 +38,7 @@ async def __call__( logger.warning("Received update without user information!") return None - if user.id not in config.TELEGRAM_ADMINS_ID: + if user.id not in EnvSettings.TELEGRAM_ADMINS_ID: logger.warning("Blocked %s", user.username or user.first_name) return None diff --git a/routers/base.py b/routers/base.py index 3fbf64c..d085d0e 100644 --- a/routers/base.py +++ b/routers/base.py @@ -8,9 +8,7 @@ from aiogram.filters.command import CommandStart, Command from aiogram.fsm.context import FSMContext -from utils.statedb import storage -from utils.lang import MessageTexts -from utils.keys import BotKeyboards +from utils import MessageTexts, storage, BotKeyboards from models import PagesCallbacks, PagesActions router = Router() diff --git a/routers/node.py b/routers/node.py index d15c11a..c63655e 100644 --- a/routers/node.py +++ b/routers/node.py @@ -7,8 +7,7 @@ from aiogram.types import CallbackQuery from db.crud import SettingManager -from utils.lang import MessageTexts -from utils.keys import BotKeyboards +from utils import MessageTexts, BotKeyboards from models import ( PagesActions, PagesCallbacks, diff --git a/routers/user.py b/routers/user.py index bc56aec..9438361 100644 --- a/routers/user.py +++ b/routers/user.py @@ -10,11 +10,15 @@ from marzban import ProxyInbound -from utils.config import MARZBAN_USERNAME -from utils.lang import MessageTexts -from utils.keys import BotKeyboards -from utils.statedb import storage -from utils import panel, text_info, helpers +from utils import ( + panel, + text_info, + helpers, + MessageTexts, + storage, + EnvSettings, + BotKeyboards, +) from models import ( PagesActions, PagesCallbacks, @@ -154,10 +158,12 @@ async def user_create_owner_select( """ await state.update_data(admin=callback_data.username) inbounds = await panel.get_inbounds() + tags = [item['tag'] for sublist in inbounds.values() for item in sublist] await state.update_data(inbounds=inbounds) + await state.update_data(selected_inbounds=tags) return await callback.message.edit_text( text=MessageTexts.ASK_CREATE_USER_INBOUNDS, - reply_markup=BotKeyboards.inbounds(inbounds), + reply_markup=BotKeyboards.inbounds(inbounds, tags), ) @@ -244,7 +250,7 @@ async def user_create_inbounds_save(callback: CallbackQuery, state: FSMContext): ) if new_user: - if data["admin"] != MARZBAN_USERNAME: + if data["admin"] != EnvSettings.MARZBAN_USERNAME: await panel.set_owner(data["admin"], new_user.username) qr_bytes = await helpers.create_qr(new_user.subscription_url) await callback.message.answer_photo( @@ -258,3 +264,8 @@ async def user_create_inbounds_save(callback: CallbackQuery, state: FSMContext): ) await callback.message.delete() + await state.clear() + new_message = await callback.message.answer( + text=MessageTexts.START, reply_markup=BotKeyboards.home() + ) + return await storage.clear_and_add_message(new_message) diff --git a/routers/users.py b/routers/users.py index 4e21225..33f6b08 100644 --- a/routers/users.py +++ b/routers/users.py @@ -14,9 +14,8 @@ BotActions, UserInboundsCallbacks, ) -from utils.lang import MessageTexts -from utils.keys import BotKeyboards -from utils import panel, helpers + +from utils import panel, helpers, BotKeyboards, MessageTexts router = Router() diff --git a/utils/__init__.py b/utils/__init__.py index bcd7730..78ec732 100644 --- a/utils/__init__.py +++ b/utils/__init__.py @@ -2,7 +2,17 @@ This module imports necessary components for database access and logging. """ -from .statedb import storage -from .log import logger +from .statedb import SQLAlchemyStorage +from .log import BotLogger +from .config import EnvFile +from .lang import MessageTextsFile, KeyboardTextsFile +from .keys import BotKeyboards -__all__ = ["storage", "logger"] +logger = BotLogger("HolderBot").get_logger() +EnvSettings = EnvFile() +MessageTexts = MessageTextsFile() +KeyboardTexts = KeyboardTextsFile() +storage = SQLAlchemyStorage() + + +__all__ = ["storage", "logger", "KeyboardTexts", "MessageTexts", "BotKeyboards"] diff --git a/utils/config.py b/utils/config.py index d567e25..cb795be 100644 --- a/utils/config.py +++ b/utils/config.py @@ -4,45 +4,19 @@ It ensures that all required settings are provided and checks for missing values. """ -from decouple import config +from pydantic_settings import BaseSettings, SettingsConfigDict -# Function to check if a required configuration value is missing -def require_setting(setting_name, value): - """ - Ensures that a required setting is provided and not empty. - """ - if not value: - raise ValueError( - f"The '{setting_name}' setting is required and cannot be empty." - ) +class EnvFile(BaseSettings): + """.env file config data""" + model_config: SettingsConfigDict = SettingsConfigDict( + env_file=".env", extra="ignore" + ) -# Telegram Bot Settings -TELEGRAM_BOT_TOKEN = config("TELEGRAM_BOT_TOKEN", cast=str) # required -require_setting("TELEGRAM_BOT_TOKEN", TELEGRAM_BOT_TOKEN) - -TELEGRAM_ADMINS_ID = config( - "TELEGRAM_ADMINS_ID", - default="", - cast=lambda v: [ - int(i) for i in filter(str.isdigit, (s.strip() for s in v.split(","))) - ], -) # required -require_setting("TELEGRAM_ADMINS_ID", TELEGRAM_ADMINS_ID) - -# Marzban Panel Settings -MARZBAN_USERNAME = config("MARZBAN_USERNAME", default="", cast=str) # required -require_setting("MARZBAN_USERNAME", MARZBAN_USERNAME) - -MARZBAN_PASSWORD = config("MARZBAN_PASSWORD", default="", cast=str) # required -require_setting("MARZBAN_PASSWORD", MARZBAN_PASSWORD) - -MARZBAN_ADDRESS = config("MARZBAN_ADDRESS", default="", cast=str) # required -require_setting("MARZBAN_ADDRESS", MARZBAN_ADDRESS) - -EXCLUDED_MONITORINGS = [ - x.strip() - for x in config("EXCLUDED_MONITORINGS", default="", cast=str).split(",") - if x.strip() -] + TELEGRAM_BOT_TOKEN: str + TELEGRAM_ADMINS_ID: list[int] + MARZBAN_USERNAME: str + MARZBAN_PASSWORD: str + MARZBAN_ADDRESS: str + EXCLUDED_MONITORINGS: list[str] = [] diff --git a/utils/helpers.py b/utils/helpers.py index 544d821..02d14be 100644 --- a/utils/helpers.py +++ b/utils/helpers.py @@ -9,8 +9,7 @@ import httpx from marzban import UserModify, UserResponse from models import AdminActions -from utils import panel -from utils.log import logger +from utils import logger, panel async def create_qr(text: str) -> bytes: diff --git a/utils/keys.py b/utils/keys.py index 8bcddcd..1d3fd75 100644 --- a/utils/keys.py +++ b/utils/keys.py @@ -9,7 +9,7 @@ from aiogram.utils.keyboard import InlineKeyboardBuilder from marzban import ProxyInbound, Admin, UserResponse -from utils.lang import KeyboardTexts +from utils.lang import KeyboardTextsFile from models import ( PagesActions, PagesCallbacks, @@ -21,6 +21,8 @@ BotActions, ) +KeyboardTexts = KeyboardTextsFile() + class BotKeyboards: """ diff --git a/utils/lang.py b/utils/lang.py index 2591695..d71b948 100644 --- a/utils/lang.py +++ b/utils/lang.py @@ -2,64 +2,78 @@ This module contains constants and texts used in the HolderBot. """ -from enum import Enum +from pydantic_settings import BaseSettings, SettingsConfigDict -# Module constants -VERSION = "0.2.3" -OWNER = "@ErfJabs" - -class KeyboardTexts(str, Enum): +class KeyboardTextsFile(BaseSettings): """Keyboard texts used in the bot.""" - HOME = "🏠 Back to home" - USER_CREATE = "πŸ‘€ User Create" - NODE_MONITORING = "πŸ—ƒ Node Monitoring" - ACTIVE = "βœ… Active" - ON_HOLD = "⏸️ On hold" - FINISH = "βœ”οΈ Finish" - NODE_MONITORING_CHECKER = "🧨 Checker" - NODE_MONITORING_AUTO_RESTART = "πŸ” AutoRestart" - USERS_MENU = "πŸ‘₯ Users" - USERS_ADD_INBOUND = "βž• Add inbound" - USERS_DELETE_INBOUND = "βž– Delete inbound" - USER_CREATE_LINK_COPY = "To copy the link, please click." + model_config: SettingsConfigDict = SettingsConfigDict( + env_file=".env", extra="ignore" + ) + + HOME: str = "🏠 Back to home" + USER_CREATE: str = "πŸ‘€ User Create" + NODE_MONITORING: str = "πŸ—ƒ Node Monitoring" + ACTIVE: str = "βœ… Active" + ON_HOLD: str = "⏸️ On hold" + FINISH: str = "βœ”οΈ Finish" + NODE_MONITORING_CHECKER: str = "🧨 Checker" + NODE_MONITORING_AUTO_RESTART: str = "πŸ” AutoRestart" + USERS_MENU: str = "πŸ‘₯ Users" + USERS_ADD_INBOUND: str = "βž• Add inbound" + USERS_DELETE_INBOUND: str = "βž– Delete inbound" + USER_CREATE_LINK_COPY: str = "To copy the link, please click." -class MessageTexts(str, Enum): +class MessageTextsFile(BaseSettings): """Message texts used in the bot.""" - START = f"Welcome to HolderBot πŸ€– [{VERSION}]\nDeveloped and designed by {OWNER}" - VERSION = f"⚑️ Current Version: {VERSION}" - ASK_CREATE_USER_BASE_USERNAME = "πŸ‘€ Please enter the user base name" - ASK_CREATE_USER_START_NUMBER = "πŸ”’ Please enter the starting user number" - ASK_CREATE_USER_HOW_MUCH = "πŸ‘₯ How many users would you like to create?" - ASK_CREATE_USER_DATA_LIMIT = "πŸ“Š Please enter the data limit in GB" - ASK_CREATE_USER_DATE_LIMIT = "πŸ“… Please enter the date limit in days" - ASK_CREATE_USER_STATUS = "πŸ”„ Select the user status" - ASK_CREATE_ADMIN_USERNAME = "πŸ‘€ Select the owner admin" - ASK_CREATE_USER_INBOUNDS = "🌐 Select the user inbounds" - JUST_NUMBER = "πŸ”’ Please enter numbers only" - NONE_USER_INBOUNDS = "⚠️ Please select an inbound first" - USER_INFO = ( + model_config: SettingsConfigDict = SettingsConfigDict( + env_file=".env", extra="ignore" + ) + + VERSION_NUMBER: str = "0.2.3" + OWNER_ID: str = "@ErfJabs" + + START: str = ( + f"Welcome to HolderBot πŸ€– [{VERSION_NUMBER}]\n" + f"Developed and designed by {OWNER_ID}" + ) + VERSION: str = f"⚑️ Current Version: {VERSION_NUMBER}" + ASK_CREATE_USER_BASE_USERNAME: str = "πŸ‘€ Please enter the user base name" + ASK_CREATE_USER_START_NUMBER: str = ( + "πŸ”’ Please enter the starting user number" + ) + ASK_CREATE_USER_HOW_MUCH: str = "πŸ‘₯ How many users would you like to create?" + ASK_CREATE_USER_DATA_LIMIT: str = "πŸ“Š Please enter the data limit in GB" + ASK_CREATE_USER_DATE_LIMIT: str = "πŸ“… Please enter the date limit in days" + ASK_CREATE_USER_STATUS: str = "πŸ”„ Select the user status" + ASK_CREATE_ADMIN_USERNAME: str = "πŸ‘€ Select the owner admin" + ASK_CREATE_USER_INBOUNDS: str = "🌐 Select the user inbounds" + JUST_NUMBER: str = "πŸ”’ Please enter numbers only" + NONE_USER_INBOUNDS: str = "⚠️ Please select an inbound first" + USER_INFO: str = ( "{status_emoji} Username: {username}\n" "πŸ“Š Data limit: {data_limit} GB\n" "πŸ“… Date limit: {date_limit} days\n" "πŸ”— Subscription: {subscription}" ) - NODE_ERROR = ( + NODE_ERROR: str = ( "πŸ—ƒ Node: {name}\n" "πŸ“ IP: {ip}\n" "πŸ“ͺ Message: {message}" ) - NODE_AUTO_RESTART_DONE = "βœ… {name} auto restart is Done!" - NODE_AUTO_RESTART_ERROR = "❌ {name} auto restart is Wrong!" - NODE_MONITORING_MENU = ( + NODE_AUTO_RESTART_DONE: str = "βœ… {name} auto restart is Done!" + NODE_AUTO_RESTART_ERROR: str = ( + "❌ {name} auto restart is Wrong!" + ) + NODE_MONITORING_MENU: str = ( "🧨 Checker is {checker}\n" "πŸ” AutoRestart is {auto_restart}" ) - USERS_MENU = "πŸ‘₯ What do you need?" - USERS_INBOUND_SELECT = "🌐 Select Your Inbound:" - WORKING = "⏳" - USERS_INBOUND_SUCCESS_UPDATED = "βœ… Users Inbounds is Updated!" - USERS_INBOUND_ERROR_UPDATED = "❌ Users Inbounds not Updated!" + USERS_MENU: str = "πŸ‘₯ What do you need?" + USERS_INBOUND_SELECT: str = "🌐 Select Your Inbound:" + WORKING: str = "⏳" + USERS_INBOUND_SUCCESS_UPDATED: str = "βœ… Users Inbounds is Updated!" + USERS_INBOUND_ERROR_UPDATED: str = "❌ Users Inbounds not Updated!" diff --git a/utils/log.py b/utils/log.py index 62b1c89..9d2c73c 100644 --- a/utils/log.py +++ b/utils/log.py @@ -8,30 +8,63 @@ import logging -def setup_logger(bot_name, level=logging.INFO): +class BotLogger: """ - Set up a logger for the specified bot. + A class to set up and manage a logger for the bot. + This class allows logging to both the console and a file. """ - bot_logger = logging.getLogger(bot_name) - bot_logger.setLevel(level) - console_handler = logging.StreamHandler() - console_handler.setLevel(logging.DEBUG) + def __init__(self, bot_name: str, level: int = logging.INFO): + """ + Initialize the logger for the specified bot. - file_handler = logging.FileHandler(f"data/{bot_name}.log") - file_handler.setLevel(logging.INFO) + Args: + bot_name (str): The name of the bot for which the logger is set up. + level (int): The logging level (default is INFO). + """ + self.bot_name = bot_name + self.level = level + self.bot_logger = logging.getLogger(bot_name) + self.bot_logger.setLevel(level) + self._setup_handlers() - formatter = logging.Formatter( - f"%(asctime)-25s | {bot_name} | %(levelname)-8s | %(message)s" - ) - console_handler.setFormatter(formatter) - file_handler.setFormatter(formatter) + def _setup_handlers(self): + """ + Set up the console and file handlers for logging. + """ + # Console handler + console_handler = logging.StreamHandler() + console_handler.setLevel(logging.DEBUG) - bot_logger.addHandler(console_handler) - bot_logger.addHandler(file_handler) + # File handler + file_handler = logging.FileHandler(f"data/{self.bot_name}.log") + file_handler.setLevel(logging.INFO) - return bot_logger + # Formatter for both handlers + formatter = logging.Formatter( + f"%(asctime)-25s | {self.bot_name} | %(levelname)-8s | %(message)s" + ) + console_handler.setFormatter(formatter) + file_handler.setFormatter(formatter) + # Add handlers to the logger + self.bot_logger.addHandler(console_handler) + self.bot_logger.addHandler(file_handler) -# Initialize the logger for HolderBot -logger = setup_logger("HolderBot") + def get_logger(self): + """ + Get the configured logger instance. + + Returns: + logging.Logger: The configured logger instance. + """ + return self.bot_logger + + def set_log_level(self, level: int): + """ + Set the logging level for the logger. + + Args: + level (int): The logging level to set. + """ + self.bot_logger.setLevel(level) diff --git a/utils/panel.py b/utils/panel.py index 5cdb09f..bb64283 100644 --- a/utils/panel.py +++ b/utils/panel.py @@ -14,10 +14,9 @@ UserModify, ) from db import TokenManager -from utils.config import MARZBAN_ADDRESS -from utils.log import logger +from utils import EnvSettings, logger -marzban_panel = MarzbanAPI(MARZBAN_ADDRESS, timeout=30.0, verify=False) +marzban_panel = MarzbanAPI(EnvSettings.MARZBAN_ADDRESS, timeout=30.0, verify=False) async def get_inbounds() -> dict[str, list[ProxyInbound]]: diff --git a/utils/report.py b/utils/report.py index 31b4e7f..79d2622 100644 --- a/utils/report.py +++ b/utils/report.py @@ -9,12 +9,11 @@ from marzban import NodeResponse -from utils.lang import MessageTexts -from utils.config import TELEGRAM_BOT_TOKEN, TELEGRAM_ADMINS_ID -from utils.log import logger +from utils import EnvSettings, MessageTexts, logger bot = Bot( - token=TELEGRAM_BOT_TOKEN, default=DefaultBotProperties(parse_mode=ParseMode.HTML) + token=EnvSettings.TELEGRAM_BOT_TOKEN, + default=DefaultBotProperties(parse_mode=ParseMode.HTML), ) @@ -23,7 +22,7 @@ async def send_message(message: str): Sends a message to all admins. """ try: - for admin_chatid in TELEGRAM_ADMINS_ID: + for admin_chatid in EnvSettings.TELEGRAM_ADMINS_ID: await bot.send_message(chat_id=admin_chatid, text=message) except (AiogramError, TelegramAPIError) as e: logger.error("Failed send report message: %s", str(e)) diff --git a/utils/statedb.py b/utils/statedb.py index b0bfe66..51f4bf9 100644 --- a/utils/statedb.py +++ b/utils/statedb.py @@ -173,6 +173,3 @@ async def clear_and_add_message( async def close(self) -> None: await self.engine.dispose() - - -storage = SQLAlchemyStorage() diff --git a/utils/text_info.py b/utils/text_info.py index dbfde44..9e23cbc 100644 --- a/utils/text_info.py +++ b/utils/text_info.py @@ -5,7 +5,7 @@ from datetime import datetime from marzban import UserResponse -from utils.lang import MessageTexts +from utils import MessageTexts def user_info(user: UserResponse) -> str: