Code Refactoring
This commit is contained in:
+164
-112
@@ -1,146 +1,198 @@
|
||||
import logging
|
||||
from pydantic_settings import BaseSettings ,SettingsConfigDict
|
||||
from pydantic import Field ,field_validator ,ValidationError
|
||||
from typing import Optional ,List ,Dict ,Any
|
||||
|
||||
class Settings (BaseSettings ):
|
||||
BOT_TOKEN :str
|
||||
ADMIN_IDS_STR :str =Field (default ="",alias ="ADMIN_IDS",description ="Comma-separated list of admin Telegram User IDs")
|
||||
|
||||
DB_NAME :str =Field (default ="bot_database.sqlite3")
|
||||
DEFAULT_LANGUAGE :str =Field (default ="ru")
|
||||
DEFAULT_CURRENCY_SYMBOL :str =Field (default ="RUB")
|
||||
|
||||
SUPPORT_LINK :Optional [str ]=Field (default =None ,description ="Link to support contact (e.g., t.me/your_support_contact)")
|
||||
SERVER_STATUS_URL :Optional [str ]=Field (default =None ,description ="Link to server status page (e.g., Uptime Kuma)")
|
||||
from pydantic_settings import BaseSettings, SettingsConfigDict
|
||||
from pydantic import Field, field_validator, ValidationError
|
||||
from typing import Optional, List, Dict, Any
|
||||
|
||||
|
||||
YOOKASSA_SHOP_ID :Optional [str ]=None
|
||||
YOOKASSA_SECRET_KEY :Optional [str ]=None
|
||||
YOOKASSA_WEBHOOK_BASE_URL :Optional [str ]=None
|
||||
YOOKASSA_RETURN_URL :Optional [str ]=None
|
||||
class Settings(BaseSettings):
|
||||
BOT_TOKEN: str
|
||||
ADMIN_IDS_STR: str = Field(
|
||||
default="",
|
||||
alias="ADMIN_IDS",
|
||||
description="Comma-separated list of admin Telegram User IDs")
|
||||
|
||||
YOOKASSA_DEFAULT_RECEIPT_EMAIL :Optional [str ]=Field (default =None ,description ="Default email for YooKassa receipts (REQUIRED for 54-FZ)")
|
||||
YOOKASSA_VAT_CODE :int =Field (default =6 ,description ="YooKassa VAT code (e.g., 1=20%, 2=10%, 4=0%, 6=No VAT - check YooKassa docs!)")
|
||||
YOOKASSA_PAYMENT_MODE :str =Field (default ="full_prepayment",description ="YooKassa payment mode (e.g., full_prepayment, full_payment)")
|
||||
YOOKASSA_PAYMENT_SUBJECT :str =Field (default ="service",description ="YooKassa payment subject (e.g., service, commodity)")
|
||||
DB_NAME: str = Field(default="bot_database.sqlite3")
|
||||
DEFAULT_LANGUAGE: str = Field(default="ru")
|
||||
DEFAULT_CURRENCY_SYMBOL: str = Field(default="RUB")
|
||||
|
||||
SUPPORT_LINK: Optional[str] = Field(
|
||||
default=None,
|
||||
description="Link to support contact (e.g., t.me/your_support_contact)"
|
||||
)
|
||||
SERVER_STATUS_URL: Optional[str] = Field(
|
||||
default=None,
|
||||
description="Link to server status page (e.g., Uptime Kuma)")
|
||||
|
||||
TELEGRAM_WEBHOOK_BASE_URL :Optional [str ]=None
|
||||
YOOKASSA_SHOP_ID: Optional[str] = None
|
||||
YOOKASSA_SECRET_KEY: Optional[str] = None
|
||||
YOOKASSA_WEBHOOK_BASE_URL: Optional[str] = None
|
||||
YOOKASSA_RETURN_URL: Optional[str] = None
|
||||
|
||||
YOOKASSA_DEFAULT_RECEIPT_EMAIL: Optional[str] = Field(
|
||||
default=None,
|
||||
description="Default email for YooKassa receipts (REQUIRED for 54-FZ)")
|
||||
YOOKASSA_VAT_CODE: int = Field(
|
||||
default=6,
|
||||
description=
|
||||
"YooKassa VAT code (e.g., 1=20%, 2=10%, 4=0%, 6=No VAT - check YooKassa docs!)"
|
||||
)
|
||||
YOOKASSA_PAYMENT_MODE: str = Field(
|
||||
default="full_prepayment",
|
||||
description=
|
||||
"YooKassa payment mode (e.g., full_prepayment, full_payment)")
|
||||
YOOKASSA_PAYMENT_SUBJECT: str = Field(
|
||||
default="service",
|
||||
description="YooKassa payment subject (e.g., service, commodity)")
|
||||
|
||||
PRICE_1_MONTH :Optional [int ]=None
|
||||
PRICE_3_MONTHS :Optional [int ]=None
|
||||
PRICE_6_MONTHS :Optional [int ]=None
|
||||
PRICE_12_MONTHS :Optional [int ]=None
|
||||
TELEGRAM_WEBHOOK_BASE_URL: Optional[str] = None
|
||||
|
||||
PRICE_1_MONTH: Optional[int] = None
|
||||
PRICE_3_MONTHS: Optional[int] = None
|
||||
PRICE_6_MONTHS: Optional[int] = None
|
||||
PRICE_12_MONTHS: Optional[int] = None
|
||||
|
||||
SUBSCRIPTION_EXPIRATION_NOTIFICATION_DAYS :int =Field (default =7 )
|
||||
SUBSCRIPTION_NOTIFICATION_HOUR_UTC :int =Field (default =9 )
|
||||
SUBSCRIPTION_NOTIFICATION_MINUTE_UTC :int =Field (default =0 )
|
||||
SUBSCRIPTION_EXPIRATION_NOTIFICATION_DAYS: int = Field(default=7)
|
||||
SUBSCRIPTION_NOTIFICATION_HOUR_UTC: int = Field(default=9)
|
||||
SUBSCRIPTION_NOTIFICATION_MINUTE_UTC: int = Field(default=0)
|
||||
|
||||
REFERRAL_BONUS_DAYS_1_MONTH: Optional[int] = 3
|
||||
REFERRAL_BONUS_DAYS_3_MONTHS: Optional[int] = 7
|
||||
REFERRAL_BONUS_DAYS_6_MONTHS: Optional[int] = 15
|
||||
REFERRAL_BONUS_DAYS_12_MONTHS: Optional[int] = 30
|
||||
REFEREE_BONUS_DAYS_1_MONTH: Optional[int] = 1
|
||||
REFEREE_BONUS_DAYS_3_MONTHS: Optional[int] = 3
|
||||
REFEREE_BONUS_DAYS_6_MONTHS: Optional[int] = 7
|
||||
REFEREE_BONUS_DAYS_12_MONTHS: Optional[int] = 15
|
||||
|
||||
REFERRAL_BONUS_DAYS_1_MONTH :Optional [int ]=3
|
||||
REFERRAL_BONUS_DAYS_3_MONTHS :Optional [int ]=7
|
||||
REFERRAL_BONUS_DAYS_6_MONTHS :Optional [int ]=15
|
||||
REFERRAL_BONUS_DAYS_12_MONTHS :Optional [int ]=30
|
||||
REFEREE_BONUS_DAYS_1_MONTH :Optional [int ]=1
|
||||
REFEREE_BONUS_DAYS_3_MONTHS :Optional [int ]=3
|
||||
REFEREE_BONUS_DAYS_6_MONTHS :Optional [int ]=7
|
||||
REFEREE_BONUS_DAYS_12_MONTHS :Optional [int ]=15
|
||||
PANEL_API_URL: Optional[str] = None
|
||||
PANEL_API_KEY: Optional[str] = None
|
||||
PANEL_USER_DEFAULT_EXPIRE_DAYS: int = Field(default=1)
|
||||
PANEL_USER_DEFAULT_TRAFFIC_BYTES: int = Field(default=0)
|
||||
PANEL_USER_DEFAULT_TRAFFIC_STRATEGY: str = Field(default="NO_RESET")
|
||||
PANEL_USER_DEFAULT_INBOUND_UUIDS: Optional[str] = Field(default=None)
|
||||
|
||||
TRIAL_ENABLED: bool = Field(default=True)
|
||||
TRIAL_DURATION_DAYS: int = Field(default=3)
|
||||
TRIAL_TRAFFIC_LIMIT_GB: Optional[float] = Field(default=5.0)
|
||||
|
||||
PANEL_API_URL :Optional [str ]=None
|
||||
PANEL_API_KEY :Optional [str ]=None
|
||||
PANEL_USER_DEFAULT_EXPIRE_DAYS :int =Field (default =1 )
|
||||
PANEL_USER_DEFAULT_TRAFFIC_BYTES :int =Field (default =0 )
|
||||
PANEL_USER_DEFAULT_TRAFFIC_STRATEGY :str =Field (default ="NO_RESET")
|
||||
PANEL_USER_DEFAULT_INBOUND_UUIDS :Optional [str ]=Field (default =None )
|
||||
WEB_SERVER_HOST: str = Field(default="0.0.0.0")
|
||||
WEB_SERVER_PORT: int = Field(default=8080)
|
||||
LOGS_PAGE_SIZE: int = Field(default=10)
|
||||
|
||||
|
||||
TRIAL_ENABLED :bool =Field (default =True )
|
||||
TRIAL_DURATION_DAYS :int =Field (default =3 )
|
||||
TRIAL_TRAFFIC_LIMIT_GB :Optional [float ]=Field (default =5.0 )
|
||||
|
||||
WEB_SERVER_HOST :str =Field (default ="0.0.0.0")
|
||||
WEB_SERVER_PORT :int =Field (default =8080 )
|
||||
LOGS_PAGE_SIZE :int =Field (default =10 )
|
||||
|
||||
_admin_ids_list :Optional [List [int ]]=None
|
||||
_admin_ids_list: Optional[List[int]] = None
|
||||
|
||||
@property
|
||||
def ADMIN_IDS (self )->List [int ]:
|
||||
if self ._admin_ids_list is None :
|
||||
if self .ADMIN_IDS_STR :
|
||||
try :
|
||||
self ._admin_ids_list =[int (admin_id .strip ())for admin_id in self .ADMIN_IDS_STR .split (',')if admin_id .strip ().isdigit ()]
|
||||
if not self ._admin_ids_list and self .ADMIN_IDS_STR :
|
||||
logging .error (f"ADMIN_IDS_STR ('{self.ADMIN_IDS_STR}') contains non-integer values or is malformed. No admin IDs loaded from string.")
|
||||
self ._admin_ids_list =[]
|
||||
except ValueError :
|
||||
logging .error (f"Invalid ADMIN_IDS_STR format: '{self.ADMIN_IDS_STR}'. Expected comma-separated integers.")
|
||||
self ._admin_ids_list =[]
|
||||
else :
|
||||
self ._admin_ids_list =[]
|
||||
return self ._admin_ids_list
|
||||
def ADMIN_IDS(self) -> List[int]:
|
||||
if self._admin_ids_list is None:
|
||||
if self.ADMIN_IDS_STR:
|
||||
try:
|
||||
self._admin_ids_list = [
|
||||
int(admin_id.strip())
|
||||
for admin_id in self.ADMIN_IDS_STR.split(',')
|
||||
if admin_id.strip().isdigit()
|
||||
]
|
||||
if not self._admin_ids_list and self.ADMIN_IDS_STR:
|
||||
logging.error(
|
||||
f"ADMIN_IDS_STR ('{self.ADMIN_IDS_STR}') contains non-integer values or is malformed. No admin IDs loaded from string."
|
||||
)
|
||||
self._admin_ids_list = []
|
||||
except ValueError:
|
||||
logging.error(
|
||||
f"Invalid ADMIN_IDS_STR format: '{self.ADMIN_IDS_STR}'. Expected comma-separated integers."
|
||||
)
|
||||
self._admin_ids_list = []
|
||||
else:
|
||||
self._admin_ids_list = []
|
||||
return self._admin_ids_list
|
||||
|
||||
@property
|
||||
def PRIMARY_ADMIN_ID (self )->Optional [int ]:
|
||||
admin_ids_list =self .ADMIN_IDS
|
||||
if admin_ids_list :
|
||||
return admin_ids_list [0 ]
|
||||
def PRIMARY_ADMIN_ID(self) -> Optional[int]:
|
||||
admin_ids_list = self.ADMIN_IDS
|
||||
if admin_ids_list:
|
||||
return admin_ids_list[0]
|
||||
return None
|
||||
|
||||
@property
|
||||
def trial_traffic_limit_bytes (self )->int :
|
||||
if self .TRIAL_TRAFFIC_LIMIT_GB is None or self .TRIAL_TRAFFIC_LIMIT_GB <=0 :return 0
|
||||
return int (self .TRIAL_TRAFFIC_LIMIT_GB *(1024 **3 ))
|
||||
def trial_traffic_limit_bytes(self) -> int:
|
||||
if self.TRIAL_TRAFFIC_LIMIT_GB is None or self.TRIAL_TRAFFIC_LIMIT_GB <= 0:
|
||||
return 0
|
||||
return int(self.TRIAL_TRAFFIC_LIMIT_GB * (1024**3))
|
||||
|
||||
@property
|
||||
def parsed_default_panel_user_inbound_uuids (self )->Optional [List [str ]]:
|
||||
if self .PANEL_USER_DEFAULT_INBOUND_UUIDS :return [uuid .strip ()for uuid in self .PANEL_USER_DEFAULT_INBOUND_UUIDS .split (',')if uuid .strip ()]
|
||||
def parsed_default_panel_user_inbound_uuids(self) -> Optional[List[str]]:
|
||||
if self.PANEL_USER_DEFAULT_INBOUND_UUIDS:
|
||||
return [
|
||||
uuid.strip()
|
||||
for uuid in self.PANEL_USER_DEFAULT_INBOUND_UUIDS.split(',')
|
||||
if uuid.strip()
|
||||
]
|
||||
return None
|
||||
|
||||
@property
|
||||
def yookassa_webhook_path (self )->str :return "/webhook/yookassa"
|
||||
def yookassa_webhook_path(self) -> str:
|
||||
return "/webhook/yookassa"
|
||||
|
||||
@property
|
||||
def yookassa_full_webhook_url (self )->Optional [str ]:
|
||||
if self .YOOKASSA_WEBHOOK_BASE_URL :return f"{self.YOOKASSA_WEBHOOK_BASE_URL.rstrip('/')}{self.yookassa_webhook_path}"
|
||||
def yookassa_full_webhook_url(self) -> Optional[str]:
|
||||
if self.YOOKASSA_WEBHOOK_BASE_URL:
|
||||
return f"{self.YOOKASSA_WEBHOOK_BASE_URL.rstrip('/')}{self.yookassa_webhook_path}"
|
||||
return None
|
||||
|
||||
@property
|
||||
def subscription_options (self )->Dict [int ,int ]:
|
||||
options :Dict [int ,int ]={};
|
||||
if self .PRICE_1_MONTH is not None :options [1 ]=self .PRICE_1_MONTH
|
||||
if self .PRICE_3_MONTHS is not None :options [3 ]=self .PRICE_3_MONTHS
|
||||
if self .PRICE_6_MONTHS is not None :options [6 ]=self .PRICE_6_MONTHS
|
||||
if self .PRICE_12_MONTHS is not None :options [12 ]=self .PRICE_12_MONTHS
|
||||
def subscription_options(self) -> Dict[int, int]:
|
||||
options: Dict[int, int] = {}
|
||||
if self.PRICE_1_MONTH is not None: options[1] = self.PRICE_1_MONTH
|
||||
if self.PRICE_3_MONTHS is not None: options[3] = self.PRICE_3_MONTHS
|
||||
if self.PRICE_6_MONTHS is not None: options[6] = self.PRICE_6_MONTHS
|
||||
if self.PRICE_12_MONTHS is not None: options[12] = self.PRICE_12_MONTHS
|
||||
return options
|
||||
|
||||
@property
|
||||
def referral_bonus_inviter (self )->Dict [int ,int ]:
|
||||
bonuses :Dict [int ,int ]={};
|
||||
if self .REFERRAL_BONUS_DAYS_1_MONTH is not None :bonuses [1 ]=self .REFERRAL_BONUS_DAYS_1_MONTH
|
||||
if self .REFERRAL_BONUS_DAYS_3_MONTHS is not None :bonuses [3 ]=self .REFERRAL_BONUS_DAYS_3_MONTHS
|
||||
if self .REFERRAL_BONUS_DAYS_6_MONTHS is not None :bonuses [6 ]=self .REFERRAL_BONUS_DAYS_6_MONTHS
|
||||
if self .REFERRAL_BONUS_DAYS_12_MONTHS is not None :bonuses [12 ]=self .REFERRAL_BONUS_DAYS_12_MONTHS
|
||||
return bonuses
|
||||
@property
|
||||
def referral_bonus_referee (self )->Dict [int ,int ]:
|
||||
bonuses :Dict [int ,int ]={};
|
||||
if self .REFEREE_BONUS_DAYS_1_MONTH is not None :bonuses [1 ]=self .REFEREE_BONUS_DAYS_1_MONTH
|
||||
if self .REFEREE_BONUS_DAYS_3_MONTHS is not None :bonuses [3 ]=self .REFEREE_BONUS_DAYS_3_MONTHS
|
||||
if self .REFEREE_BONUS_DAYS_6_MONTHS is not None :bonuses [6 ]=self .REFEREE_BONUS_DAYS_6_MONTHS
|
||||
if self .REFEREE_BONUS_DAYS_12_MONTHS is not None :bonuses [12 ]=self .REFEREE_BONUS_DAYS_12_MONTHS
|
||||
def referral_bonus_inviter(self) -> Dict[int, int]:
|
||||
bonuses: Dict[int, int] = {}
|
||||
if self.REFERRAL_BONUS_DAYS_1_MONTH is not None:
|
||||
bonuses[1] = self.REFERRAL_BONUS_DAYS_1_MONTH
|
||||
if self.REFERRAL_BONUS_DAYS_3_MONTHS is not None:
|
||||
bonuses[3] = self.REFERRAL_BONUS_DAYS_3_MONTHS
|
||||
if self.REFERRAL_BONUS_DAYS_6_MONTHS is not None:
|
||||
bonuses[6] = self.REFERRAL_BONUS_DAYS_6_MONTHS
|
||||
if self.REFERRAL_BONUS_DAYS_12_MONTHS is not None:
|
||||
bonuses[12] = self.REFERRAL_BONUS_DAYS_12_MONTHS
|
||||
return bonuses
|
||||
|
||||
model_config =SettingsConfigDict (env_file ='.env',env_file_encoding ='utf-8',extra ='ignore',populate_by_name =True )
|
||||
@property
|
||||
def referral_bonus_referee(self) -> Dict[int, int]:
|
||||
bonuses: Dict[int, int] = {}
|
||||
if self.REFEREE_BONUS_DAYS_1_MONTH is not None:
|
||||
bonuses[1] = self.REFEREE_BONUS_DAYS_1_MONTH
|
||||
if self.REFEREE_BONUS_DAYS_3_MONTHS is not None:
|
||||
bonuses[3] = self.REFEREE_BONUS_DAYS_3_MONTHS
|
||||
if self.REFEREE_BONUS_DAYS_6_MONTHS is not None:
|
||||
bonuses[6] = self.REFEREE_BONUS_DAYS_6_MONTHS
|
||||
if self.REFEREE_BONUS_DAYS_12_MONTHS is not None:
|
||||
bonuses[12] = self.REFEREE_BONUS_DAYS_12_MONTHS
|
||||
return bonuses
|
||||
|
||||
_settings_instance :Optional [Settings ]=None
|
||||
def get_settings ()->Settings :
|
||||
model_config = SettingsConfigDict(env_file='.env',
|
||||
env_file_encoding='utf-8',
|
||||
extra='ignore',
|
||||
populate_by_name=True)
|
||||
|
||||
|
||||
_settings_instance: Optional[Settings] = None
|
||||
|
||||
|
||||
def get_settings() -> Settings:
|
||||
global _settings_instance
|
||||
if _settings_instance is None :
|
||||
try :
|
||||
_settings_instance =Settings ()
|
||||
if not _settings_instance .ADMIN_IDS :
|
||||
logging .warning ("CRITICAL: ADMIN_IDS not set or contains no valid integer IDs in .env. Admin functionality will be restricted.")
|
||||
except ValidationError as e :
|
||||
logging .critical (f"Pydantic validation error while loading settings: {e}")
|
||||
raise SystemExit (f"CRITICAL SETTINGS ERROR: {e}")
|
||||
return _settings_instance
|
||||
if _settings_instance is None:
|
||||
try:
|
||||
_settings_instance = Settings()
|
||||
if not _settings_instance.ADMIN_IDS:
|
||||
logging.warning(
|
||||
"CRITICAL: ADMIN_IDS not set or contains no valid integer IDs in .env. Admin functionality will be restricted."
|
||||
)
|
||||
except ValidationError as e:
|
||||
logging.critical(
|
||||
f"Pydantic validation error while loading settings: {e}")
|
||||
raise SystemExit(f"CRITICAL SETTINGS ERROR: {e}")
|
||||
return _settings_instance
|
||||
|
||||
Reference in New Issue
Block a user