Skip to content

Commit

Permalink
refact: refact log, lang, config reader and fix some bugs
Browse files Browse the repository at this point in the history
  • Loading branch information
erfjab committed Nov 6, 2024
1 parent 0e56233 commit 89016a0
Show file tree
Hide file tree
Showing 20 changed files with 195 additions and 159 deletions.
4 changes: 2 additions & 2 deletions .env.example
Original file line number Diff line number Diff line change
@@ -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'
EXCLUDED_MONITORINGS=[NODE_NAME_ONE , NODE_NAME_TWO]
9 changes: 4 additions & 5 deletions jobs/node_monitoring.py
Original file line number Diff line number Diff line change
Expand Up @@ -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():
Expand All @@ -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"]:
Expand All @@ -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:
Expand Down
2 changes: 1 addition & 1 deletion jobs/scheduler.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()

Expand Down
9 changes: 4 additions & 5 deletions jobs/token_updater.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
32 changes: 18 additions & 14 deletions main.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
),
Expand All @@ -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)
4 changes: 2 additions & 2 deletions middlewares/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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

Expand Down
4 changes: 1 addition & 3 deletions routers/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
3 changes: 1 addition & 2 deletions routers/node.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
25 changes: 18 additions & 7 deletions routers/user.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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),
)


Expand Down Expand Up @@ -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(
Expand All @@ -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)
5 changes: 2 additions & 3 deletions routers/users.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()

Expand Down
16 changes: 13 additions & 3 deletions utils/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"]
50 changes: 12 additions & 38 deletions utils/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -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] = []
3 changes: 1 addition & 2 deletions utils/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
4 changes: 3 additions & 1 deletion utils/keys.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -21,6 +21,8 @@
BotActions,
)

KeyboardTexts = KeyboardTextsFile()


class BotKeyboards:
"""
Expand Down
Loading

0 comments on commit 89016a0

Please sign in to comment.