mirror of
https://github.com/BEDOLAGA-DEV/remnawave-bedolaga-telegram-bot.git
synced 2026-01-20 03:40:26 +00:00
220 lines
6.7 KiB
Python
220 lines
6.7 KiB
Python
import re
|
|
from typing import Optional, Union
|
|
from datetime import datetime
|
|
import html
|
|
|
|
ALLOWED_HTML_TAGS = {
|
|
'b', 'strong',
|
|
'i', 'em',
|
|
'u', 'ins',
|
|
's', 'strike', 'del',
|
|
'code',
|
|
'pre',
|
|
'a',
|
|
'blockquote',
|
|
'spoiler', 'tg-spoiler'
|
|
}
|
|
|
|
def validate_email(email: str) -> bool:
|
|
pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
|
|
return re.match(pattern, email) is not None
|
|
|
|
|
|
def validate_phone(phone: str) -> bool:
|
|
pattern = r'^\+?[1-9]\d{1,14}$'
|
|
cleaned_phone = re.sub(r'[\s\-\(\)]', '', phone)
|
|
return re.match(pattern, cleaned_phone) is not None
|
|
|
|
|
|
def validate_telegram_username(username: str) -> bool:
|
|
if not username:
|
|
return False
|
|
username = username.lstrip('@')
|
|
pattern = r'^[a-zA-Z0-9_]{5,32}$'
|
|
return re.match(pattern, username) is not None
|
|
|
|
|
|
def validate_promocode(code: str) -> bool:
|
|
if not code or len(code) < 3 or len(code) > 20:
|
|
return False
|
|
return code.replace('_', '').replace('-', '').isalnum()
|
|
|
|
|
|
def validate_amount(amount_str: str, min_amount: float = 0, max_amount: float = float('inf')) -> Optional[float]:
|
|
try:
|
|
amount = float(amount_str.replace(',', '.'))
|
|
if min_amount <= amount <= max_amount:
|
|
return amount
|
|
return None
|
|
except (ValueError, TypeError):
|
|
return None
|
|
|
|
|
|
def validate_positive_integer(value: Union[str, int], max_value: int = None) -> Optional[int]:
|
|
try:
|
|
num = int(value)
|
|
if num > 0 and (max_value is None or num <= max_value):
|
|
return num
|
|
return None
|
|
except (ValueError, TypeError):
|
|
return None
|
|
|
|
|
|
def validate_date_string(date_str: str, date_format: str = "%Y-%m-%d") -> Optional[datetime]:
|
|
try:
|
|
return datetime.strptime(date_str, date_format)
|
|
except ValueError:
|
|
return None
|
|
|
|
|
|
def validate_url(url: str) -> bool:
|
|
pattern = r'^https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)$'
|
|
return re.match(pattern, url) is not None
|
|
|
|
|
|
def validate_uuid(uuid_str: str) -> bool:
|
|
pattern = r'^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$'
|
|
return re.match(pattern, uuid_str.lower()) is not None
|
|
|
|
|
|
def validate_traffic_amount(traffic_str: str) -> Optional[int]:
|
|
traffic_str = traffic_str.upper().strip()
|
|
|
|
if traffic_str in ['UNLIMITED', 'БЕЗЛИМИТ', '∞']:
|
|
return 0
|
|
|
|
units = {
|
|
'MB': 1,
|
|
'GB': 1024,
|
|
'TB': 1024 * 1024,
|
|
'МБ': 1,
|
|
'ГБ': 1024,
|
|
'ТБ': 1024 * 1024
|
|
}
|
|
|
|
for unit, multiplier in units.items():
|
|
if traffic_str.endswith(unit):
|
|
try:
|
|
value = float(traffic_str[:-len(unit)].strip())
|
|
return int(value * multiplier)
|
|
except ValueError:
|
|
break
|
|
|
|
try:
|
|
return int(float(traffic_str))
|
|
except ValueError:
|
|
return None
|
|
|
|
|
|
def validate_subscription_period(days: Union[str, int]) -> Optional[int]:
|
|
try:
|
|
days_int = int(days)
|
|
if 1 <= days_int <= 3650:
|
|
return days_int
|
|
return None
|
|
except (ValueError, TypeError):
|
|
return None
|
|
|
|
|
|
def sanitize_html(text: str) -> str:
|
|
allowed_tags = ['b', 'strong', 'i', 'em', 'u', 'ins', 's', 'strike', 'del', 'code', 'pre']
|
|
|
|
for tag in allowed_tags:
|
|
text = re.sub(f'<{tag}>', f'<{tag}>', text, flags=re.IGNORECASE)
|
|
text = re.sub(f'</{tag}>', f'</{tag}>', text, flags=re.IGNORECASE)
|
|
|
|
text = re.sub(r'<(?!/?(?:' + '|'.join(allowed_tags) + r')\b)[^>]*>', '', text)
|
|
|
|
return text
|
|
|
|
|
|
def validate_device_count(count: Union[str, int]) -> Optional[int]:
|
|
try:
|
|
count_int = int(count)
|
|
if 1 <= count_int <= 10:
|
|
return count_int
|
|
return None
|
|
except (ValueError, TypeError):
|
|
return None
|
|
|
|
|
|
def validate_referral_code(code: str) -> bool:
|
|
if not code:
|
|
return False
|
|
|
|
if code.startswith('ref') and len(code) > 3:
|
|
user_id_part = code[3:]
|
|
return user_id_part.isdigit()
|
|
|
|
return validate_promocode(code)
|
|
|
|
def validate_html_tags(text: str) -> tuple[bool, str]:
|
|
if not text:
|
|
return True, ""
|
|
|
|
tag_pattern = r'<(/?)([a-zA-Z][a-zA-Z0-9-]*)[^>]*>'
|
|
tags = re.findall(tag_pattern, text)
|
|
|
|
for is_closing, tag_name in tags:
|
|
tag_name_lower = tag_name.lower()
|
|
|
|
if tag_name_lower not in ALLOWED_HTML_TAGS:
|
|
return False, f"Неподдерживаемый тег: <{tag_name}>"
|
|
|
|
tag_stack = []
|
|
for is_closing, tag_name in tags:
|
|
tag_name_lower = tag_name.lower()
|
|
|
|
if not is_closing:
|
|
tag_stack.append(tag_name_lower)
|
|
else:
|
|
if not tag_stack:
|
|
return False, f"Закрывающий тег без открывающего: </{tag_name}>"
|
|
|
|
last_tag = tag_stack.pop()
|
|
if last_tag != tag_name_lower:
|
|
return False, f"Неправильная вложенность тегов: ожидался </{last_tag}>, найден </{tag_name}>"
|
|
|
|
if tag_stack:
|
|
return False, f"Незакрытый тег: <{tag_stack[-1]}>"
|
|
|
|
return True, ""
|
|
|
|
def sanitize_html_advanced(text: str) -> str:
|
|
if not text:
|
|
return text
|
|
|
|
text = html.escape(text)
|
|
|
|
for tag in ALLOWED_HTML_TAGS:
|
|
text = re.sub(
|
|
f'<{tag}(>|\\s[^&]*>)',
|
|
lambda m: m.group(0).replace('<', '<').replace('>', '>'),
|
|
text,
|
|
flags=re.IGNORECASE
|
|
)
|
|
text = re.sub(
|
|
f'</{tag}>',
|
|
f'</{tag}>',
|
|
text,
|
|
flags=re.IGNORECASE
|
|
)
|
|
|
|
return text
|
|
|
|
def get_html_help_text() -> str:
|
|
"""Возвращает текст справки по HTML тегам"""
|
|
return """<b>Поддерживаемые HTML теги:</b>
|
|
|
|
- <code><b>жирный</b></code> или <code><strong>жирный</strong></code>
|
|
- <code><i>курсив</i></code> или <code><em>курсив</em></code>
|
|
- <code><u>подчеркнутый</u></code>
|
|
- <code><s>зачеркнутый</s></code>
|
|
- <code><code>моноширинный</code></code>
|
|
- <code><pre>блок кода</pre></code>
|
|
- <code><a href="url">ссылка</a></code>
|
|
- <code><blockquote>цитата</blockquote></code>
|
|
- <code><spoiler>спойлер</spoiler></code>
|
|
|
|
<b>Неподдерживаемые теги:</b> <br>, <p>, <div>, <span> и другие"""
|