"""Load settings from environment / .env file.""" from __future__ import annotations import hashlib import os from dataclasses import dataclass from pathlib import Path from urllib.parse import quote_plus from dotenv import load_dotenv _ENV_FILE = (os.getenv("ENV_FILE") or "").strip() load_dotenv(_ENV_FILE or None) ROOT_DIR = Path(__file__).resolve().parent @dataclass(frozen=True, slots=True) class Settings: remnawave_base_url: str remnawave_token: str bot_token: str database_url: str admin_ids: tuple[int, ...] = () caddy_token: str | None = None remnawave_username: str = "" remnawave_password: str = "" tariffs_path: str = str(ROOT_DIR / "tariffs.json") crypto_bot_token: str = "" crypto_pay_testnet: bool = False heleket_merchant_id: str = "" heleket_api_key: str = "" heleket_callback_url: str = "" heleket_currency: str = "RUB" digiseller_seller_id: str = "" digiseller_api_key: str = "" digiseller_product_ids: tuple[int, ...] = () digiseller_pay_url: str = "" digiseller_credit_from: str = "cnt_goods" digiseller_callback_url: str = "" lava_shop_id: str = "" lava_secret_key: str = "" lava_additional_key: str = "" lava_hook_url: str = "" lava_success_url: str = "" lava_fail_url: str = "" lava_expire_minutes: int = 60 anypay_api_id: str = "" anypay_api_key: str = "" anypay_project_id: str = "" anypay_currency: str = "RUB" anypay_method: str = "" anypay_success_url: str = "" anypay_fail_url: str = "" anypay_email: str = "" anypay_check_ip: bool = False webapp_url: str = "" webapp_host: str = "0.0.0.0" webapp_port: int = 8080 topup_code_pay_url: str = "" web_admin_login: str = "" web_admin_password: str = "" stars_enabled: bool = True stars_rub_per_star: float = 1.5 smtp_host: str = "" smtp_port: int = 587 smtp_user: str = "" smtp_password: str = "" smtp_from: str = "" smtp_tls: bool = True smtp_ssl: bool = False telegram_client_id: str = "" telegram_webhook_enabled: bool = False telegram_webhook_path: str = "/telegram/webhook" telegram_webhook_secret: str = "" telegram_webhook_url: str = "" @property def web_admin_enabled(self) -> bool: return bool(self.web_admin_login and self.web_admin_password) @property def effective_telegram_client_id(self) -> str: if (self.telegram_client_id or "").strip(): return self.telegram_client_id.strip() from services.tg_webapp import telegram_client_id_from_token return telegram_client_id_from_token(self.bot_token) @property def effective_telegram_webhook_path(self) -> str: path = (self.telegram_webhook_path or "/telegram/webhook").strip() or "/telegram/webhook" if not path.startswith("/"): path = f"/{path}" return path.rstrip("/") or "/telegram/webhook" @property def effective_telegram_webhook_url(self) -> str: explicit = (self.telegram_webhook_url or "").strip().rstrip("/") if explicit: return explicit base = (self.webapp_url or "").strip().rstrip("/") if not base: return "" return f"{base}{self.effective_telegram_webhook_path}" def _parse_env_secret(raw: str) -> str: """Значение из .env без кавычек и пробелов по краям.""" s = (raw or "").strip() if len(s) >= 2 and s[0] == s[-1] and s[0] in "\"'": s = s[1:-1].strip() return s def _parse_admin_ids(raw: str) -> tuple[int, ...]: if not raw.strip(): return () result: list[int] = [] for part in raw.split(","): part = part.strip() if not part: continue result.append(int(part)) return tuple(result) def _build_database_url() -> str: explicit = (os.getenv("DATABASE_URL") or "").strip() if explicit: return explicit user = (os.getenv("POSTGRES_USER") or "vpnbot").strip() password = (os.getenv("POSTGRES_PASSWORD") or "vpnbot").strip() host = (os.getenv("POSTGRES_HOST") or "db").strip() port = (os.getenv("POSTGRES_PORT") or "5432").strip() name = (os.getenv("POSTGRES_DB") or "vpnbot").strip() return f"postgresql://{quote_plus(user)}:{quote_plus(password)}@{host}:{port}/{name}" def _parse_int_ids(raw: str) -> tuple[int, ...]: if not raw.strip(): return () result: list[int] = [] for part in raw.replace(";", ",").split(","): part = part.strip() if not part: continue result.append(int(part)) return tuple(result) def get_settings(*, require_bot_token: bool = False) -> Settings: base_url = (os.getenv("REMNAWAVE_BASE_URL") or "").rstrip("/") token = (os.getenv("REMNAWAVE_TOKEN") or "").strip() bot_token = (os.getenv("BOT_TOKEN") or "").strip() caddy = (os.getenv("CADDY_TOKEN") or "").strip() or None admin_ids = _parse_admin_ids(os.getenv("ADMIN_IDS") or "") tariffs_path = (os.getenv("TARIFFS_PATH") or str(ROOT_DIR / "tariffs.json")).strip() crypto_bot_token = (os.getenv("CRYPTO_BOT_TOKEN") or "").strip() crypto_pay_testnet = (os.getenv("CRYPTO_PAY_TESTNET") or "").strip().lower() in { "1", "true", "yes", "on", } remnawave_username = (os.getenv("REMNAWAVE_USERNAME") or "").strip() remnawave_password = (os.getenv("REMNAWAVE_PASSWORD") or "").strip() webapp_url = (os.getenv("WEBAPP_URL") or "").strip().rstrip("/") webapp_host = (os.getenv("WEBAPP_HOST") or "0.0.0.0").strip() webapp_port = int((os.getenv("WEBAPP_PORT") or "8080").strip()) database_url = _build_database_url() heleket_merchant_id = (os.getenv("HELEKET_MERCHANT_ID") or "").strip() heleket_api_key = (os.getenv("HELEKET_API_KEY") or "").strip() heleket_callback_url = (os.getenv("HELEKET_CALLBACK_URL") or "").strip().rstrip("/") if not heleket_callback_url and webapp_url: heleket_callback_url = f"{webapp_url}/api/payments/heleket" heleket_currency = (os.getenv("HELEKET_CURRENCY") or "RUB").strip().upper() or "RUB" digiseller_seller_id = (os.getenv("DIGISELLER_SELLER_ID") or "").strip() digiseller_api_key = (os.getenv("DIGISELLER_API_KEY") or "").strip() digiseller_product_ids = _parse_int_ids(os.getenv("DIGISELLER_PRODUCT_IDS") or "") digiseller_pay_url = (os.getenv("DIGISELLER_PAY_URL") or "").strip() digiseller_credit_from = ( os.getenv("DIGISELLER_CREDIT_FROM") or "cnt_goods" ).strip().lower() or "cnt_goods" digiseller_callback_url = ( os.getenv("DIGISELLER_CALLBACK_URL") or "" ).strip().rstrip("/") if not digiseller_callback_url and webapp_url: digiseller_callback_url = f"{webapp_url}/api/payments/digiseller" lava_shop_id = (os.getenv("LAVA_SHOP_ID") or "").strip() lava_secret_key = (os.getenv("LAVA_SECRET_KEY") or "").strip() lava_additional_key = (os.getenv("LAVA_ADDITIONAL_KEY") or "").strip() lava_hook_url = (os.getenv("LAVA_HOOK_URL") or "").strip().rstrip("/") if not lava_hook_url and webapp_url: lava_hook_url = f"{webapp_url}/api/payments/lava" lava_success_url = (os.getenv("LAVA_SUCCESS_URL") or "").strip() if not lava_success_url and webapp_url: lava_success_url = f"{webapp_url}/pay/lava/success" lava_fail_url = (os.getenv("LAVA_FAIL_URL") or "").strip() if not lava_fail_url and webapp_url: lava_fail_url = f"{webapp_url}/pay/lava/fail" try: lava_expire_minutes = int((os.getenv("LAVA_EXPIRE_MINUTES") or "60").strip()) except ValueError: lava_expire_minutes = 60 anypay_api_id = (os.getenv("ANYPAY_API_ID") or "").strip() anypay_api_key = (os.getenv("ANYPAY_API_KEY") or "").strip() anypay_project_id = (os.getenv("ANYPAY_PROJECT_ID") or "").strip() anypay_currency = (os.getenv("ANYPAY_CURRENCY") or "RUB").strip().upper() or "RUB" anypay_method = (os.getenv("ANYPAY_METHOD") or "").strip().lower() anypay_success_url = (os.getenv("ANYPAY_SUCCESS_URL") or "").strip() if not anypay_success_url and webapp_url: anypay_success_url = f"{webapp_url}/pay/anypay/success" anypay_fail_url = (os.getenv("ANYPAY_FAIL_URL") or "").strip() if not anypay_fail_url and webapp_url: anypay_fail_url = f"{webapp_url}/pay/anypay/fail" anypay_email = (os.getenv("ANYPAY_EMAIL") or "").strip() anypay_check_ip = (os.getenv("ANYPAY_CHECK_IP") or "").strip().lower() in { "1", "true", "yes", "on", } topup_code_pay_url = (os.getenv("TOPUP_CODE_PAY_URL") or "").strip() web_admin_login = _parse_env_secret(os.getenv("WEB_ADMIN_LOGIN") or "") web_admin_password = _parse_env_secret(os.getenv("WEB_ADMIN_PASSWORD") or "") stars_enabled = (os.getenv("STARS_ENABLED") or "true").strip().lower() in { "1", "true", "yes", "on", } try: stars_rub_per_star = float((os.getenv("STARS_RUB_PER_STAR") or "1.5").strip()) except ValueError: stars_rub_per_star = 1.5 if stars_rub_per_star <= 0: stars_rub_per_star = 1.5 smtp_host = (os.getenv("SMTP_HOST") or "").strip() try: smtp_port = int((os.getenv("SMTP_PORT") or "587").strip()) except ValueError: smtp_port = 587 smtp_user = (os.getenv("SMTP_USER") or "").strip() smtp_password = os.getenv("SMTP_PASSWORD") or "" smtp_from = (os.getenv("SMTP_FROM") or "").strip() smtp_tls = (os.getenv("SMTP_TLS") or "true").strip().lower() in { "1", "true", "yes", "on", } smtp_ssl = (os.getenv("SMTP_SSL") or "").strip().lower() in { "1", "true", "yes", "on", } telegram_client_id = (os.getenv("TELEGRAM_CLIENT_ID") or "").strip() webhook_env = (os.getenv("TELEGRAM_WEBHOOK") or "").strip().lower() if webhook_env in {"1", "true", "yes", "on"}: telegram_webhook_enabled = True elif webhook_env in {"0", "false", "no", "off"}: telegram_webhook_enabled = False else: # По умолчанию webhook, если кабинет уже на публичном HTTPS telegram_webhook_enabled = webapp_url.startswith("https://") telegram_webhook_path = (os.getenv("TELEGRAM_WEBHOOK_PATH") or "/telegram/webhook").strip() telegram_webhook_secret = _parse_env_secret(os.getenv("TELEGRAM_WEBHOOK_SECRET") or "") if not telegram_webhook_secret and bot_token and telegram_webhook_enabled: telegram_webhook_secret = hashlib.sha256( f"tg-webhook:{bot_token}".encode("utf-8") ).hexdigest()[:48] telegram_webhook_url = (os.getenv("TELEGRAM_WEBHOOK_URL") or "").strip().rstrip("/") if not base_url: raise SystemExit( "REMNAWAVE_BASE_URL не задан. Скопируйте .env.example → .env и заполните." ) if not token and not (remnawave_username and remnawave_password): raise SystemExit( "Задайте REMNAWAVE_TOKEN или пару REMNAWAVE_USERNAME / REMNAWAVE_PASSWORD." ) if require_bot_token and not bot_token: raise SystemExit( "BOT_TOKEN не задан. Создайте бота у @BotFather и добавьте токен в .env." ) return Settings( remnawave_base_url=base_url, remnawave_token=token, bot_token=bot_token, database_url=database_url, admin_ids=admin_ids, caddy_token=caddy, remnawave_username=remnawave_username, remnawave_password=remnawave_password, tariffs_path=tariffs_path, crypto_bot_token=crypto_bot_token, crypto_pay_testnet=crypto_pay_testnet, heleket_merchant_id=heleket_merchant_id, heleket_api_key=heleket_api_key, heleket_callback_url=heleket_callback_url, heleket_currency=heleket_currency, digiseller_seller_id=digiseller_seller_id, digiseller_api_key=digiseller_api_key, digiseller_product_ids=digiseller_product_ids, digiseller_pay_url=digiseller_pay_url, digiseller_credit_from=digiseller_credit_from, digiseller_callback_url=digiseller_callback_url, lava_shop_id=lava_shop_id, lava_secret_key=lava_secret_key, lava_additional_key=lava_additional_key, lava_hook_url=lava_hook_url, lava_success_url=lava_success_url, lava_fail_url=lava_fail_url, lava_expire_minutes=lava_expire_minutes, anypay_api_id=anypay_api_id, anypay_api_key=anypay_api_key, anypay_project_id=anypay_project_id, anypay_currency=anypay_currency, anypay_method=anypay_method, anypay_success_url=anypay_success_url, anypay_fail_url=anypay_fail_url, anypay_email=anypay_email, anypay_check_ip=anypay_check_ip, webapp_url=webapp_url, webapp_host=webapp_host, webapp_port=webapp_port, topup_code_pay_url=topup_code_pay_url, web_admin_login=web_admin_login, web_admin_password=web_admin_password, smtp_host=smtp_host, smtp_port=smtp_port, smtp_user=smtp_user, smtp_password=smtp_password, smtp_from=smtp_from, smtp_tls=smtp_tls, smtp_ssl=smtp_ssl, stars_enabled=stars_enabled, stars_rub_per_star=stars_rub_per_star, telegram_client_id=telegram_client_id, telegram_webhook_enabled=telegram_webhook_enabled, telegram_webhook_path=telegram_webhook_path, telegram_webhook_secret=telegram_webhook_secret, telegram_webhook_url=telegram_webhook_url, )