mirror of
https://github.com/BEDOLAGA-DEV/remnawave-bedolaga-telegram-bot.git
synced 2026-01-20 11:50:27 +00:00
238 lines
7.2 KiB
Python
238 lines
7.2 KiB
Python
from datetime import datetime, timedelta
|
||
from typing import Union, Optional
|
||
|
||
|
||
def format_datetime(dt: Union[datetime, str], format_str: str = "%d.%m.%Y %H:%M") -> str:
|
||
if isinstance(dt, str):
|
||
if dt == "now" or dt == "":
|
||
dt = datetime.now()
|
||
else:
|
||
try:
|
||
dt = datetime.fromisoformat(dt.replace('Z', '+00:00'))
|
||
except (ValueError, AttributeError):
|
||
dt = datetime.now()
|
||
|
||
return dt.strftime(format_str)
|
||
|
||
|
||
def format_date(dt: Union[datetime, str], format_str: str = "%d.%m.%Y") -> str:
|
||
if isinstance(dt, str):
|
||
if dt == "now" or dt == "":
|
||
dt = datetime.now()
|
||
else:
|
||
try:
|
||
dt = datetime.fromisoformat(dt.replace('Z', '+00:00'))
|
||
except (ValueError, AttributeError):
|
||
dt = datetime.now()
|
||
|
||
return dt.strftime(format_str)
|
||
|
||
|
||
def format_time_ago(dt: Union[datetime, str], language: str = "ru") -> str:
|
||
if isinstance(dt, str):
|
||
if dt == "now" or dt == "":
|
||
dt = datetime.now()
|
||
else:
|
||
try:
|
||
dt = datetime.fromisoformat(dt.replace('Z', '+00:00'))
|
||
except (ValueError, AttributeError):
|
||
dt = datetime.now()
|
||
|
||
now = datetime.utcnow()
|
||
diff = now - dt
|
||
|
||
language_code = (language or "ru").split("-")[0].lower()
|
||
|
||
if diff.days > 0:
|
||
if diff.days == 1:
|
||
return "yesterday" if language_code == "en" else "вчера"
|
||
if diff.days < 7:
|
||
value = diff.days
|
||
if language_code == "en":
|
||
suffix = "day" if value == 1 else "days"
|
||
return f"{value} {suffix} ago"
|
||
return f"{value} дн. назад"
|
||
if diff.days < 30:
|
||
value = diff.days // 7
|
||
if language_code == "en":
|
||
suffix = "week" if value == 1 else "weeks"
|
||
return f"{value} {suffix} ago"
|
||
return f"{value} нед. назад"
|
||
if diff.days < 365:
|
||
value = diff.days // 30
|
||
if language_code == "en":
|
||
suffix = "month" if value == 1 else "months"
|
||
return f"{value} {suffix} ago"
|
||
return f"{value} мес. назад"
|
||
value = diff.days // 365
|
||
if language_code == "en":
|
||
suffix = "year" if value == 1 else "years"
|
||
return f"{value} {suffix} ago"
|
||
return f"{value} г. назад"
|
||
|
||
if diff.seconds > 3600:
|
||
value = diff.seconds // 3600
|
||
if language_code == "en":
|
||
suffix = "hour" if value == 1 else "hours"
|
||
return f"{value} {suffix} ago"
|
||
return f"{value} ч. назад"
|
||
|
||
if diff.seconds > 60:
|
||
value = diff.seconds // 60
|
||
if language_code == "en":
|
||
suffix = "minute" if value == 1 else "minutes"
|
||
return f"{value} {suffix} ago"
|
||
return f"{value} мин. назад"
|
||
|
||
return "just now" if language_code == "en" else "только что"
|
||
|
||
def format_days_declension(days: int, language: str = "ru") -> str:
|
||
if language != "ru":
|
||
return f"{days} day{'s' if days != 1 else ''}"
|
||
|
||
if days % 10 == 1 and days % 100 != 11:
|
||
return f"{days} день"
|
||
elif days % 10 in [2, 3, 4] and days % 100 not in [12, 13, 14]:
|
||
return f"{days} дня"
|
||
else:
|
||
return f"{days} дней"
|
||
|
||
|
||
def format_duration(seconds: int) -> str:
|
||
if seconds < 60:
|
||
return f"{seconds} сек."
|
||
|
||
minutes = seconds // 60
|
||
if minutes < 60:
|
||
return f"{minutes} мин."
|
||
|
||
hours = minutes // 60
|
||
if hours < 24:
|
||
return f"{hours} ч."
|
||
|
||
days = hours // 24
|
||
return f"{days} дн."
|
||
|
||
|
||
def format_bytes(bytes_value: int) -> str:
|
||
if bytes_value == 0:
|
||
return "0 B"
|
||
|
||
units = ["B", "KB", "MB", "GB", "TB"]
|
||
size = float(bytes_value)
|
||
unit_index = 0
|
||
|
||
while size >= 1024 and unit_index < len(units) - 1:
|
||
size /= 1024
|
||
unit_index += 1
|
||
|
||
if size == int(size):
|
||
return f"{int(size)} {units[unit_index]}"
|
||
else:
|
||
return f"{size:.1f} {units[unit_index]}"
|
||
|
||
|
||
def format_percentage(value: float, decimals: int = 1) -> str:
|
||
return f"{value:.{decimals}f}%"
|
||
|
||
|
||
def format_number(number: Union[int, float], separator: str = " ") -> str:
|
||
if isinstance(number, float):
|
||
integer_part = int(number)
|
||
decimal_part = number - integer_part
|
||
|
||
formatted_integer = f"{integer_part:,}".replace(",", separator)
|
||
|
||
if decimal_part > 0:
|
||
return f"{formatted_integer}.{decimal_part:.2f}".split('.')[0] + f".{str(decimal_part).split('.')[1][:2]}"
|
||
else:
|
||
return formatted_integer
|
||
else:
|
||
return f"{number:,}".replace(",", separator)
|
||
|
||
|
||
def format_price_range(min_price: int, max_price: int) -> str:
|
||
from app.config import settings
|
||
|
||
min_formatted = settings.format_price(min_price)
|
||
max_formatted = settings.format_price(max_price)
|
||
|
||
if min_price == max_price:
|
||
return min_formatted
|
||
else:
|
||
return f"{min_formatted} - {max_formatted}"
|
||
|
||
|
||
def truncate_text(text: str, max_length: int = 100, suffix: str = "...") -> str:
|
||
if len(text) <= max_length:
|
||
return text
|
||
|
||
return text[:max_length - len(suffix)] + suffix
|
||
|
||
|
||
def format_username(username: Optional[str], user_id: int, full_name: Optional[str] = None) -> str:
|
||
if full_name:
|
||
return full_name
|
||
elif username:
|
||
return f"@{username}"
|
||
else:
|
||
return f"ID{user_id}"
|
||
|
||
|
||
def format_subscription_status(
|
||
is_active: bool,
|
||
is_trial: bool,
|
||
end_date: Union[datetime, str],
|
||
language: str = "ru"
|
||
) -> str:
|
||
|
||
if isinstance(end_date, str):
|
||
try:
|
||
end_date = datetime.fromisoformat(end_date.replace('Z', '+00:00'))
|
||
except (ValueError, AttributeError):
|
||
end_date = datetime.now()
|
||
|
||
if not is_active:
|
||
return "❌ Неактивна" if language == "ru" else "❌ Inactive"
|
||
|
||
if is_trial:
|
||
status = "🎁 Тестовая" if language == "ru" else "🎁 Trial"
|
||
else:
|
||
status = "✅ Активна" if language == "ru" else "✅ Active"
|
||
|
||
now = datetime.utcnow()
|
||
if end_date > now:
|
||
days_left = (end_date - now).days
|
||
if days_left > 0:
|
||
status += f" ({days_left} дн.)" if language == "ru" else f" ({days_left} days)"
|
||
else:
|
||
hours_left = (end_date - now).seconds // 3600
|
||
status += f" ({hours_left} ч.)" if language == "ru" else f" ({hours_left} hrs)"
|
||
else:
|
||
status = "⏰ Истекла" if language == "ru" else "⏰ Expired"
|
||
|
||
return status
|
||
|
||
|
||
def format_traffic_usage(used_gb: float, limit_gb: int, language: str = "ru") -> str:
|
||
|
||
if limit_gb == 0:
|
||
if language == "ru":
|
||
return f"{used_gb:.1f} ГБ / ∞"
|
||
else:
|
||
return f"{used_gb:.1f} GB / ∞"
|
||
|
||
percentage = (used_gb / limit_gb) * 100 if limit_gb > 0 else 0
|
||
|
||
if language == "ru":
|
||
return f"{used_gb:.1f} ГБ / {limit_gb} ГБ ({percentage:.1f}%)"
|
||
else:
|
||
return f"{used_gb:.1f} GB / {limit_gb} GB ({percentage:.1f}%)"
|
||
|
||
|
||
def format_boolean(value: bool, language: str = "ru") -> str:
|
||
if language == "ru":
|
||
return "✅ Да" if value else "❌ Нет"
|
||
else:
|
||
return "✅ Yes" if value else "❌ No"
|