first commit
This commit is contained in:
0
bot/utils/__init__.py
Normal file
0
bot/utils/__init__.py
Normal file
34
bot/utils/amount_parser.py
Normal file
34
bot/utils/amount_parser.py
Normal file
@@ -0,0 +1,34 @@
|
||||
from decimal import Decimal, InvalidOperation
|
||||
import re
|
||||
|
||||
|
||||
def parse_rub_amount(raw_value: str) -> Decimal | None:
|
||||
normalized_value = raw_value.lower().strip().replace(",", ".")
|
||||
normalized_value = normalized_value.replace(" ", "")
|
||||
normalized_value = re.sub(r"руб(лей|ля|ль|ле|\.?)", "", normalized_value)
|
||||
normalized_value = re.sub(r"[^\d.]", "", normalized_value)
|
||||
|
||||
if not normalized_value or normalized_value.count(".") > 1:
|
||||
return None
|
||||
|
||||
try:
|
||||
amount = Decimal(normalized_value)
|
||||
except InvalidOperation:
|
||||
return None
|
||||
|
||||
if amount <= 0:
|
||||
return None
|
||||
|
||||
if amount.as_tuple().exponent < -2:
|
||||
return None
|
||||
|
||||
return amount.quantize(Decimal("0.01"))
|
||||
|
||||
|
||||
def format_rub_amount(amount: Decimal) -> str:
|
||||
normalized_amount = amount.quantize(Decimal("0.01"))
|
||||
|
||||
if normalized_amount == normalized_amount.to_integral():
|
||||
return f"{int(normalized_amount)} ₽"
|
||||
|
||||
return f"{normalized_amount:.2f} ₽".replace(".", ",")
|
||||
17
bot/utils/cfg_loader.py
Normal file
17
bot/utils/cfg_loader.py
Normal file
@@ -0,0 +1,17 @@
|
||||
import simplejson as json
|
||||
|
||||
# init
|
||||
CFG_PATH = "cfg/config.json"
|
||||
|
||||
|
||||
# load cfg and return it
|
||||
def load_config(cfg_path=CFG_PATH):
|
||||
|
||||
with open(cfg_path, "r", encoding="utf-8") as config_fp:
|
||||
return json.load(config_fp)
|
||||
|
||||
|
||||
def rewrite_config(obj, cfg_path=CFG_PATH):
|
||||
|
||||
with open(cfg_path, "w", encoding="utf-8") as config_fp:
|
||||
json.dump(obj, config_fp, indent=4)
|
||||
103
bot/utils/logging_config.py
Normal file
103
bot/utils/logging_config.py
Normal file
@@ -0,0 +1,103 @@
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
from datetime import datetime, timezone
|
||||
|
||||
|
||||
STANDARD_LOG_RECORD_FIELDS = {
|
||||
"args",
|
||||
"asctime",
|
||||
"created",
|
||||
"exc_info",
|
||||
"exc_text",
|
||||
"filename",
|
||||
"funcName",
|
||||
"levelname",
|
||||
"levelno",
|
||||
"lineno",
|
||||
"module",
|
||||
"msecs",
|
||||
"message",
|
||||
"msg",
|
||||
"name",
|
||||
"pathname",
|
||||
"process",
|
||||
"processName",
|
||||
"relativeCreated",
|
||||
"stack_info",
|
||||
"thread",
|
||||
"threadName",
|
||||
"taskName",
|
||||
}
|
||||
|
||||
|
||||
class JsonFormatter(logging.Formatter):
|
||||
|
||||
def __init__(self, service_name: str):
|
||||
super().__init__()
|
||||
self.service_name = service_name
|
||||
|
||||
def format(self, record: logging.LogRecord) -> str:
|
||||
log_payload = {
|
||||
"timestamp": datetime.fromtimestamp(
|
||||
record.created, tz=timezone.utc
|
||||
).isoformat(),
|
||||
"level": record.levelname,
|
||||
"service": self.service_name,
|
||||
"logger": record.name,
|
||||
"message": record.getMessage(),
|
||||
"module": record.module,
|
||||
"function": record.funcName,
|
||||
"line": record.lineno,
|
||||
}
|
||||
|
||||
extra_fields = self._collect_extra_fields(record)
|
||||
if extra_fields:
|
||||
log_payload["extra"] = extra_fields
|
||||
|
||||
if record.exc_info:
|
||||
log_payload["exception"] = self.formatException(record.exc_info)
|
||||
|
||||
return json.dumps(log_payload, ensure_ascii=False)
|
||||
|
||||
def _collect_extra_fields(self, record: logging.LogRecord) -> dict:
|
||||
extra_fields = {}
|
||||
|
||||
for key, value in record.__dict__.items():
|
||||
if key in STANDARD_LOG_RECORD_FIELDS or key.startswith("_"):
|
||||
continue
|
||||
|
||||
if isinstance(value, (str, int, float, bool)) or value is None:
|
||||
extra_fields[key] = value
|
||||
else:
|
||||
extra_fields[key] = repr(value)
|
||||
|
||||
return extra_fields
|
||||
|
||||
|
||||
def setup_logging(service_name: str) -> None:
|
||||
log_level_name = os.getenv("LOG_LEVEL", "INFO").upper()
|
||||
log_level = getattr(logging, log_level_name, logging.INFO)
|
||||
|
||||
root_logger = logging.getLogger()
|
||||
root_logger.handlers.clear()
|
||||
root_logger.setLevel(log_level)
|
||||
|
||||
handler = logging.StreamHandler(sys.stdout)
|
||||
handler.setFormatter(JsonFormatter(service_name=service_name))
|
||||
root_logger.addHandler(handler)
|
||||
|
||||
for logger_name in (
|
||||
"uvicorn",
|
||||
"uvicorn.error",
|
||||
"uvicorn.access",
|
||||
"aiogram",
|
||||
"aiohttp",
|
||||
"asyncio",
|
||||
):
|
||||
logger = logging.getLogger(logger_name)
|
||||
logger.handlers.clear()
|
||||
logger.propagate = True
|
||||
|
||||
logging.captureWarnings(True)
|
||||
44
bot/utils/text_tools.py
Normal file
44
bot/utils/text_tools.py
Normal file
@@ -0,0 +1,44 @@
|
||||
import re
|
||||
|
||||
|
||||
def to_html(obj):
|
||||
|
||||
return str(obj).replace("<", "<").replace(">", ">")
|
||||
|
||||
|
||||
def parse_links_to_inline_markup(message: str) -> list:
|
||||
"""
|
||||
Парсит сообщение с форматированными ссылками и возвращает список рядов кнопок.
|
||||
|
||||
Формат входного сообщения:
|
||||
- [Текст кнопки + Ссылка] для одной кнопки.
|
||||
- [Кнопка1 + Ссылка1][Кнопка2 + Ссылка2] для нескольких кнопок в одном ряду.
|
||||
- Каждая строка представляет отдельный ряд кнопок.
|
||||
|
||||
Пример:
|
||||
[Кнопка1 + https://example.com]
|
||||
[Кнопка2 + https://example.org][Кнопка3 + https://example.net]
|
||||
|
||||
:param message: Строка с отформатированными ссылками.
|
||||
:return: Список рядов кнопок, где каждый ряд — это список кортежей (Текст, Ссылка).
|
||||
"""
|
||||
# Исправленное регулярное выражение для поиска [Текст + Ссылка]
|
||||
pattern = re.compile(r"\[([^\[\]+]+)\s*\+\s*(https?://[^\[\]]+)\]")
|
||||
|
||||
# Инициализируем список рядов кнопок
|
||||
keyboard_rows = []
|
||||
|
||||
# Разбиваем сообщение на строки
|
||||
lines = message.strip().split("\n")
|
||||
|
||||
for line in lines:
|
||||
# Находим все совпадения в строке
|
||||
matches = pattern.findall(line)
|
||||
if matches:
|
||||
row = []
|
||||
for text, url in matches:
|
||||
button = (text.strip(), url.strip())
|
||||
row.append(button)
|
||||
keyboard_rows.append(row)
|
||||
|
||||
return keyboard_rows
|
||||
Reference in New Issue
Block a user