/
servgri
/
interview
Обзор
Документация
Войти
/
servgri
/
interview
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
master
telegram_bot/utils.py
162 строки
5 KB
Seal
initial
27 ноя 2025, 03:03
27 ноя 2025, 03:03
447211e
Код
Авторство
О чём код?
from datetime import datetime from typing import List from PIL import Image, ImageDraw, ImageFont import logging import os # ===================================================================== # ЛОГИРОВАНИЕ # ===================================================================== def setup_logger(name: str) -> logging.Logger: """ Создаёт и настраивает логгер (без дублирования обработчиков). """ log_dir = "logs" os.makedirs(log_dir, exist_ok=True) logger = logging.getLogger(name) logger.setLevel(logging.INFO) if logger.handlers: return logger formatter = logging.Formatter( fmt="%(asctime)s - %(name)s - %(levelname)s - %(message)s", datefmt="%Y-%m-%d %H:%M:%S" ) file_handler = logging.FileHandler( os.path.join(log_dir, "bot_logs.txt"), encoding="utf-8" ) file_handler.setLevel(logging.DEBUG) file_handler.setFormatter(formatter) stream_handler = logging.StreamHandler() stream_handler.setLevel(logging.INFO) stream_handler.setFormatter(formatter) logger.addHandler(file_handler) logger.addHandler(stream_handler) return logger logger = setup_logger(__name__) # ===================================================================== # РЕНДЕРИНГ ИЗОБРАЖЕНИЙ И ТЕКСТА # ===================================================================== def load_font_chain(size: int) -> ImageFont.FreeTypeFont: """ Загружает подходящий шрифт из возможных. """ priorities = [ "arial.ttf", "LiberationSans-Regular.ttf" ] for path in priorities: try: f = ImageFont.truetype(path, size=size) logger.debug(f"Использован шрифт {path}") return f except Exception: continue logger.warning("Использован стандартный шрифт") return ImageFont.load_default() def wrap_text(text: str, font, max_width: int, draw: ImageDraw.Draw) -> List[str]: """ Корректно переносит текст на строки по пиксельной ширине. """ lines = [] for paragraph in text.strip().split("\n"): paragraph = paragraph.strip() if not paragraph: lines.append("") continue words = paragraph.split() current_line = "" for word in words: test_line = word if not current_line else current_line + " " + word w = draw.textlength(test_line, font=font) if w <= max_width: current_line = test_line else: if current_line: lines.append(current_line) current_line = word if current_line: lines.append(current_line) return lines def render_task_as_image( text: str, title: str, task_id: int, width: int = 800, height: int = 600, bg_color: str = "white", text_color: str = "black", title_color: str = "black", font_size: int = 18, title_size: int = 24, margin: int = 30, line_spacing: int = 28, ) -> Image.Image: """ Генерирует изображение с задачей. """ logger.debug(f"Рендер изображения задачи {task_id}") img = Image.new("RGB", (width, height), color=bg_color) draw = ImageDraw.Draw(img) font = load_font_chain(font_size) title_font = load_font_chain(title_size) # Заголовок header_text = f"{title} #{task_id}" issue_date = f"Выдано: {datetime.now():%d.%m.%Y}" def center_x(text, fnt): return (width - draw.textlength(text, font=fnt)) // 2 y = margin draw.text((center_x(header_text, title_font), y), header_text, fill=title_color, font=title_font) y += title_size + 10 draw.text((center_x(issue_date, font), y), issue_date, fill=text_color, font=font) y += font_size + 20 # Основной текст max_width = width - 2 * margin wrapped_lines = wrap_text(text, font, max_width, draw) for line in wrapped_lines: if y + line_spacing > height - margin: draw.text((center_x("…", font), y), "…", fill=text_color, font=font) logger.debug("Обрезка текста по высоте") break if not line: y += line_spacing // 2 continue draw.text((center_x(line, font), y), line, fill=text_color, font=font) y += line_spacing logger.info(f"Изображение задачи {task_id} готово") return img