v2.0
This commit is contained in:
305
app/config.py
Normal file
305
app/config.py
Normal file
@@ -0,0 +1,305 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
from functools import cached_property
|
||||
from urllib.parse import quote_plus, urlparse
|
||||
|
||||
from pydantic import Field
|
||||
from pydantic_settings import BaseSettings, SettingsConfigDict
|
||||
|
||||
|
||||
@dataclass(frozen=True, slots=True)
|
||||
class PaymentPlanSettings:
|
||||
code: str
|
||||
days: int
|
||||
amount_rub: int
|
||||
|
||||
|
||||
class Settings(BaseSettings):
|
||||
model_config = SettingsConfigDict(
|
||||
env_file=".env",
|
||||
env_file_encoding="utf-8",
|
||||
extra="ignore",
|
||||
)
|
||||
|
||||
bot_token: str = Field(validation_alias="BOT_TOKEN")
|
||||
bot_admin_ids_raw: str = Field(default="", validation_alias="BOT_ADMIN_IDS")
|
||||
bot_moderator_ids_raw: str = Field(default="", validation_alias="BOT_MODERATOR_IDS")
|
||||
telegram_proxy_url: str = Field(default="", validation_alias="TELEGRAM_PROXY_URL")
|
||||
bot_brand_name: str = Field(default="OREOL VPN", validation_alias="BOT_BRAND_NAME")
|
||||
bot_public_username: str = Field(default="", validation_alias="BOT_PUBLIC_USERNAME")
|
||||
bot_support_url: str = Field(default="", validation_alias="BOT_SUPPORT_URL")
|
||||
bot_terms_url: str = Field(default="", validation_alias="BOT_TERMS_URL")
|
||||
bot_support_ticket_link: str = Field(default="", validation_alias="BOT_SUPPORT_TICKET_LINK")
|
||||
bot_support_ticket_chat_id_raw: str = Field(
|
||||
default="",
|
||||
validation_alias="BOT_SUPPORT_TICKET_CHAT_ID",
|
||||
)
|
||||
bot_support_ticket_thread_id: int = Field(
|
||||
default=0,
|
||||
validation_alias="BOT_SUPPORT_TICKET_THREAD_ID",
|
||||
)
|
||||
bot_start_image_enabled: bool = Field(
|
||||
default=True,
|
||||
validation_alias="BOT_START_IMAGE_ENABLED",
|
||||
)
|
||||
bot_start_image_path: str = Field(default="", validation_alias="BOT_START_IMAGE_PATH")
|
||||
|
||||
remnawave_base_url: str = Field(validation_alias="REMNAWAVE_BASE_URL")
|
||||
remnawave_api_token: str = Field(default="", validation_alias="REMNAWAVE_API_TOKEN")
|
||||
remnawave_caddy_api_key: str = Field(default="", validation_alias="REMNAWAVE_CADDY_API_KEY")
|
||||
remnawave_timeout_seconds: float = Field(
|
||||
default=20.0,
|
||||
validation_alias="REMNAWAVE_TIMEOUT_SECONDS",
|
||||
)
|
||||
|
||||
db_host: str = Field(default="127.0.0.1", validation_alias="DB_HOST")
|
||||
db_port: int = Field(default=3306, validation_alias="DB_PORT")
|
||||
db_name: str = Field(default="telegabot", validation_alias="DB_NAME")
|
||||
db_user: str = Field(default="telegabot", validation_alias="DB_USER")
|
||||
db_password: str = Field(default="", validation_alias="DB_PASSWORD")
|
||||
db_echo: bool = Field(default=False, validation_alias="DB_ECHO")
|
||||
create_database_on_start: bool = Field(
|
||||
default=True,
|
||||
validation_alias="CREATE_DATABASE_ON_START",
|
||||
)
|
||||
create_tables_on_start: bool = Field(
|
||||
default=True,
|
||||
validation_alias="CREATE_TABLES_ON_START",
|
||||
)
|
||||
sync_subscription_history: bool = Field(
|
||||
default=True,
|
||||
validation_alias="SYNC_SUBSCRIPTION_HISTORY",
|
||||
)
|
||||
sync_batch_size: int = Field(default=100, validation_alias="SYNC_BATCH_SIZE")
|
||||
log_level: str = Field(default="INFO", validation_alias="LOG_LEVEL")
|
||||
|
||||
payment_plans_raw: str = Field(
|
||||
default="30:250,180:600,365:1000",
|
||||
validation_alias="PAYMENT_PLANS",
|
||||
)
|
||||
payment_transfer_text: str = Field(
|
||||
default="Переведите оплату по указанным реквизитам и отправьте чек в бот.",
|
||||
validation_alias="PAYMENT_TRANSFER_TEXT",
|
||||
)
|
||||
payment_review_link: str = Field(default="", validation_alias="PAYMENT_REVIEW_LINK")
|
||||
payment_review_chat_id_raw: str = Field(
|
||||
default="",
|
||||
validation_alias="PAYMENT_REVIEW_CHAT_ID",
|
||||
)
|
||||
payment_review_thread_id: int = Field(
|
||||
default=0,
|
||||
validation_alias="PAYMENT_REVIEW_THREAD_ID",
|
||||
)
|
||||
referral_discount_percent: int = Field(
|
||||
default=5,
|
||||
validation_alias="REFERRAL_DISCOUNT_PERCENT",
|
||||
)
|
||||
referral_bonus_days: int = Field(
|
||||
default=7,
|
||||
validation_alias="REFERRAL_BONUS_DAYS",
|
||||
)
|
||||
payment_plan_code: str = Field(default="vpn_30d", validation_alias="PAYMENT_PLAN_CODE")
|
||||
payment_plan_title: str = Field(
|
||||
default="OREOL VPN на 30 дней",
|
||||
validation_alias="PAYMENT_PLAN_TITLE",
|
||||
)
|
||||
payment_plan_description: str = Field(
|
||||
default="Доступ к VPN на 30 дней",
|
||||
validation_alias="PAYMENT_PLAN_DESCRIPTION",
|
||||
)
|
||||
payment_plan_price_stars: int = Field(default=500, validation_alias="PAYMENT_PLAN_PRICE_STARS")
|
||||
payment_plan_duration_days: int = Field(
|
||||
default=30,
|
||||
validation_alias="PAYMENT_PLAN_DURATION_DAYS",
|
||||
)
|
||||
payment_plan_traffic_limit_gb: int = Field(
|
||||
default=0,
|
||||
validation_alias="PAYMENT_PLAN_TRAFFIC_LIMIT_GB",
|
||||
)
|
||||
payment_plan_traffic_reset_period: str = Field(
|
||||
default="NO_RESET",
|
||||
validation_alias="PAYMENT_PLAN_TRAFFIC_RESET_PERIOD",
|
||||
)
|
||||
payment_internal_squad_uuids_raw: str = Field(
|
||||
default="",
|
||||
validation_alias="PAYMENT_INTERNAL_SQUAD_UUIDS",
|
||||
)
|
||||
payment_external_squad_uuid: str = Field(
|
||||
default="",
|
||||
validation_alias="PAYMENT_EXTERNAL_SQUAD_UUID",
|
||||
)
|
||||
payment_support_text: str = Field(
|
||||
default="Если оплата прошла, но доступ не выдался, напишите в поддержку.",
|
||||
validation_alias="PAYMENT_SUPPORT_TEXT",
|
||||
)
|
||||
payment_username_prefix: str = Field(
|
||||
default="Oreol",
|
||||
validation_alias="PAYMENT_USERNAME_PREFIX",
|
||||
)
|
||||
payment_user_tag: str = Field(default="BOT", validation_alias="PAYMENT_USER_TAG")
|
||||
|
||||
@cached_property
|
||||
def bot_admin_ids(self) -> set[int]:
|
||||
values: set[int] = set()
|
||||
for raw_part in self.bot_admin_ids_raw.split(","):
|
||||
part = raw_part.strip()
|
||||
if part:
|
||||
values.add(int(part))
|
||||
return values
|
||||
|
||||
@cached_property
|
||||
def bot_moderator_ids(self) -> set[int]:
|
||||
values: set[int] = set()
|
||||
for raw_part in self.bot_moderator_ids_raw.split(","):
|
||||
part = raw_part.strip()
|
||||
if part:
|
||||
values.add(int(part))
|
||||
return values
|
||||
|
||||
@property
|
||||
def database_url(self) -> str:
|
||||
user = quote_plus(self.db_user)
|
||||
password = quote_plus(self.db_password)
|
||||
return (
|
||||
f"mysql+aiomysql://{user}:{password}"
|
||||
f"@{self.db_host}:{self.db_port}/{self.db_name}?charset=utf8mb4"
|
||||
)
|
||||
|
||||
@property
|
||||
def database_server_url(self) -> str:
|
||||
user = quote_plus(self.db_user)
|
||||
password = quote_plus(self.db_password)
|
||||
return (
|
||||
f"mysql+aiomysql://{user}:{password}"
|
||||
f"@{self.db_host}:{self.db_port}/?charset=utf8mb4"
|
||||
)
|
||||
|
||||
@cached_property
|
||||
def support_ticket_chat_id(self) -> int | None:
|
||||
if self.bot_support_ticket_chat_id_raw.strip():
|
||||
return int(self.bot_support_ticket_chat_id_raw.strip())
|
||||
|
||||
parsed_chat_id, _ = self._parse_private_topic_link(self.bot_support_ticket_link)
|
||||
return parsed_chat_id
|
||||
|
||||
@cached_property
|
||||
def support_ticket_message_thread_id(self) -> int | None:
|
||||
if self.bot_support_ticket_thread_id > 0:
|
||||
return self.bot_support_ticket_thread_id
|
||||
|
||||
_, parsed_thread_id = self._parse_private_topic_link(self.bot_support_ticket_link)
|
||||
return parsed_thread_id
|
||||
|
||||
@cached_property
|
||||
def payment_review_chat_id(self) -> int | None:
|
||||
if self.payment_review_chat_id_raw.strip():
|
||||
return int(self.payment_review_chat_id_raw.strip())
|
||||
|
||||
parsed_chat_id, _ = self._parse_private_topic_link(self.payment_review_link)
|
||||
return parsed_chat_id
|
||||
|
||||
@cached_property
|
||||
def payment_review_message_thread_id(self) -> int | None:
|
||||
if self.payment_review_thread_id > 0:
|
||||
return self.payment_review_thread_id
|
||||
|
||||
_, parsed_thread_id = self._parse_private_topic_link(self.payment_review_link)
|
||||
return parsed_thread_id
|
||||
|
||||
@staticmethod
|
||||
def _parse_private_topic_link(link: str) -> tuple[int | None, int | None]:
|
||||
cleaned = link.strip()
|
||||
if not cleaned:
|
||||
return None, None
|
||||
|
||||
parsed = urlparse(cleaned)
|
||||
if parsed.netloc not in {"t.me", "telegram.me", "www.t.me", "www.telegram.me"}:
|
||||
return None, None
|
||||
|
||||
parts = [part for part in parsed.path.split("/") if part]
|
||||
if len(parts) < 3 or parts[0] != "c" or not parts[1].isdigit():
|
||||
return None, None
|
||||
|
||||
chat_id = int(f"-100{parts[1]}")
|
||||
thread_id = int(parts[2]) if len(parts) >= 4 and parts[2].isdigit() else None
|
||||
return chat_id, thread_id
|
||||
|
||||
def is_admin(self, telegram_id: int | None) -> bool:
|
||||
return telegram_id is not None and telegram_id in self.bot_admin_ids
|
||||
|
||||
def is_moderator(self, telegram_id: int | None) -> bool:
|
||||
return telegram_id is not None and telegram_id in self.bot_moderator_ids
|
||||
|
||||
@property
|
||||
def bot_public_username_normalized(self) -> str:
|
||||
return self.bot_public_username.strip().removeprefix("@")
|
||||
|
||||
@cached_property
|
||||
def payment_internal_squad_uuids(self) -> list[str]:
|
||||
values: list[str] = []
|
||||
for raw_part in self.payment_internal_squad_uuids_raw.split(","):
|
||||
part = raw_part.strip()
|
||||
if part:
|
||||
values.append(part)
|
||||
return values
|
||||
|
||||
@cached_property
|
||||
def payment_external_squad_uuid_normalized(self) -> str | None:
|
||||
value = self.payment_external_squad_uuid.strip()
|
||||
return value or None
|
||||
|
||||
@cached_property
|
||||
def payment_username_prefix_normalized(self) -> str:
|
||||
normalized = "".join(
|
||||
ch for ch in self.payment_username_prefix.strip() if ch.isalnum() or ch in "_-"
|
||||
)
|
||||
return normalized[:12] or "Oreol"
|
||||
|
||||
@cached_property
|
||||
def payment_user_tag_normalized(self) -> str | None:
|
||||
normalized = "".join(
|
||||
ch for ch in self.payment_user_tag.strip().upper() if ch.isalnum() or ch == "_"
|
||||
)
|
||||
return normalized[:16] or None
|
||||
|
||||
@cached_property
|
||||
def payment_plans(self) -> list[PaymentPlanSettings]:
|
||||
values: list[PaymentPlanSettings] = []
|
||||
for raw_part in self.payment_plans_raw.split(","):
|
||||
part = raw_part.strip()
|
||||
if not part:
|
||||
continue
|
||||
|
||||
fragments = [fragment.strip() for fragment in part.split(":")]
|
||||
if len(fragments) != 2 or not fragments[0].isdigit() or not fragments[1].isdigit():
|
||||
continue
|
||||
|
||||
days = int(fragments[0])
|
||||
amount_rub = int(fragments[1])
|
||||
if days <= 0 or amount_rub <= 0:
|
||||
continue
|
||||
|
||||
values.append(
|
||||
PaymentPlanSettings(
|
||||
code=f"{days}d",
|
||||
days=days,
|
||||
amount_rub=amount_rub,
|
||||
)
|
||||
)
|
||||
|
||||
if values:
|
||||
return sorted(values, key=lambda item: item.days)
|
||||
|
||||
fallback_amount = max(self.payment_plan_price_stars, 0)
|
||||
fallback_days = max(self.payment_plan_duration_days, 0)
|
||||
if fallback_amount > 0 and fallback_days > 0:
|
||||
return [
|
||||
PaymentPlanSettings(
|
||||
code=self.payment_plan_code.strip() or f"{fallback_days}d",
|
||||
days=fallback_days,
|
||||
amount_rub=fallback_amount,
|
||||
)
|
||||
]
|
||||
|
||||
return []
|
||||
Reference in New Issue
Block a user