From 780669b958eea78de4e6f274893a34cff75a1b29 Mon Sep 17 00:00:00 2001 From: TBS093A Date: Wed, 14 May 2025 16:47:19 +0200 Subject: [PATCH] feat(telegram client): adjust format of displayed text and etc - --- src/handlers.py | 198 +++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 170 insertions(+), 28 deletions(-) diff --git a/src/handlers.py b/src/handlers.py index d2fe399..eb2f918 100644 --- a/src/handlers.py +++ b/src/handlers.py @@ -4,7 +4,7 @@ from telegram import Update from telegram.ext import ContextTypes from telegram.constants import ParseMode from .youtube_utils import extract_youtube_urls, extract_video_id, get_transcript -from .openai_utils import summarize_long_text +from .openai_utils import summarize_long_text, chunk_text, summarize_text from .db import save_video_summary, check_if_url_exists from .config import TRANSCRIPT_LANGUAGES @@ -40,7 +40,8 @@ async def handle_message(update: Update, context: ContextTypes.DEFAULT_TYPE): # Opcjonalnie: sprawdź, czy już istnieje w bazie, zanim zaczniesz przetwarzać # if await check_if_url_exists(url): # logger.info(f"URL {url} już istnieje w bazie danych. Pomijam.") - # await context.bot.send_message( + # await safe_send_message( + # context.bot, # chat_id=chat_id, # text=f"Informacje o filmie {url} są już w bazie.", # disable_web_page_preview=True @@ -63,7 +64,8 @@ async def handle_message(update: Update, context: ContextTypes.DEFAULT_TYPE): title = f"Film YouTube {video_id}" # Użyj zastępczego tytułu except Exception as e: logger.warning(f"Nie udało się pobrać transkrypcji dla ID filmu: {video_id}: {str(e)}") - await context.bot.send_message( + await safe_send_message( + context.bot, chat_id=chat_id, text=f"Nie udało się pobrać transkrypcji dla filmu: {url}", disable_web_page_preview=True @@ -71,40 +73,125 @@ async def handle_message(update: Update, context: ContextTypes.DEFAULT_TYPE): processed_urls_in_message.add(url) continue # Potrzebujemy transkrypcji do streszczenia - await context.bot.send_chat_action(chat_id=chat_id, action='typing') + # Informuj o rozpoczęciu przetwarzania + await safe_send_message( + context.bot, + chat_id=chat_id, + text=f"*Rozpoczynam przetwarzanie filmu:*\n{escape_markdown_v2(title)}\n\n*Link:* {escape_markdown_v2(url)}", + parse_mode=ParseMode.MARKDOWN_V2, + disable_web_page_preview=True + ) - # Wygeneruj streszczenie - summary = await summarize_long_text(transcript) - if not summary: - logger.error(f"Nie udało się wygenerować streszczenia dla ID filmu: {video_id}") - await context.bot.send_message( + # Podziel tekst na fragmenty do przetworzenia + chunks = await chunk_text(transcript) + all_summaries = [] + combined_summary = "" + + # Dla każdego fragmentu, wygeneruj i wyślij streszczenie + for i, chunk in enumerate(chunks): + await context.bot.send_chat_action(chat_id=chat_id, action='typing') + progress_msg = f"Przetwarzanie fragmentu {i+1}/{len(chunks)}..." + logger.info(progress_msg) + + # Jeśli jest więcej niż jeden fragment, powiadom użytkownika o postępie + if len(chunks) > 1 and i == 0: + await safe_send_message( + context.bot, + chat_id=chat_id, + text=f"Film jest długi, podzielono transkrypcję na {len(chunks)} części. Przetwarzam każdą z nich...", + disable_web_page_preview=True + ) + + try: + # Generuj streszczenie fragmentu + partial_summary = await summarize_text( + chunk, + is_partial=len(chunks) > 1, + part_num=i+1, + total_parts=len(chunks) + ) + all_summaries.append(partial_summary) + + # Jeśli mamy więcej niż jeden fragment, wysyłaj postęp na bieżąco + if len(chunks) > 1: + part_header = f"*Streszczenie - część {i+1}/{len(chunks)}:*\n\n" + response_text = f"{part_header}{escape_markdown_v2(partial_summary)}" + + await send_long_message( + context.bot, + chat_id=chat_id, + text=response_text, + parse_mode=ParseMode.MARKDOWN_V2, + disable_web_page_preview=True + ) + except Exception as e: + logger.error(f"Błąd podczas streszczania fragmentu {i+1}: {str(e)}") + await safe_send_message( + context.bot, + chat_id=chat_id, + text=f"Wystąpił błąd podczas przetwarzania fragmentu {i+1}/{len(chunks)}: {str(e)}", + disable_web_page_preview=True + ) + + # Jeśli mamy więcej niż 3 fragmenty, generuj końcowe streszczenie + if len(chunks) > 3: + await context.bot.send_chat_action(chat_id=chat_id, action='typing') + await safe_send_message( + context.bot, chat_id=chat_id, - text=f"Nie udało się wygenerować streszczenia dla filmu: {title} ({url})", + text="Tworzę ostateczne streszczenie łączące wszystkie fragmenty...", disable_web_page_preview=True ) - processed_urls_in_message.add(url) - continue + + try: + # Połącz wszystkie częściowe streszczenia + combined_text = "\n\n".join(all_summaries) + combined_summary = await summarize_text( + combined_text, + is_final_summary=True + ) + except Exception as e: + logger.error(f"Błąd podczas generowania końcowego streszczenia: {str(e)}") + # Jeśli końcowe streszczenie się nie powiedzie, użyj połączonych częściowych + combined_summary = "\n\n".join(all_summaries) + await safe_send_message( + context.bot, + chat_id=chat_id, + text="Nie udało się wygenerować końcowego streszczenia. Wyświetlam połączone częściowe streszczenia.", + disable_web_page_preview=True + ) + else: + # Dla mniejszej liczby fragmentów po prostu połącz streszczenia + combined_summary = "\n\n".join(all_summaries) - # Zapisz do bazy danych - saved = await save_video_summary(url, title, transcript, summary) - if saved: - logger.info(f"Pomyślnie przetworzono i zapisano film: {title} ({url})") - response_text = ( - f"*Przetworzono film:* {escape_markdown_v2(title)}\n\n" - f"*Link:* {escape_markdown_v2(url)}\n\n" - f"*Streszczenie:*\n{escape_markdown_v2(summary)}" - ) - # Użyj funkcji do wysyłania długich wiadomości + # Zapisz pełne streszczenie do bazy danych + saved = await save_video_summary(url, title, transcript, combined_summary) + + # Wyślij końcowe streszczenie, jeśli były więcej niż 3 fragmenty + if len(chunks) > 3: + final_text = f"*Ostateczne streszczenie filmu:*\n*{escape_markdown_v2(title)}*\n\n{escape_markdown_v2(combined_summary)}" await send_long_message( context.bot, chat_id=chat_id, - text=response_text, + text=final_text, parse_mode=ParseMode.MARKDOWN_V2, disable_web_page_preview=True ) + + # Podsumowanie procesu + if saved: + logger.info(f"Pomyślnie przetworzono i zapisano film: {title} ({url})") + if len(chunks) <= 3: # Nie wysyłaj podsumowania ponownie dla dłuższych filmów + await safe_send_message( + context.bot, + chat_id=chat_id, + text=f"Pomyślnie zapisano streszczenie filmu w bazie danych: {title}", + disable_web_page_preview=True + ) else: logger.error(f"Nie udało się zapisać danych do bazy dla filmu: {title} ({url})") - await context.bot.send_message( + await safe_send_message( + context.bot, chat_id=chat_id, text=f"Wystąpił błąd podczas zapisywania danych dla filmu: {title} ({url})", disable_web_page_preview=True @@ -115,8 +202,61 @@ async def handle_message(update: Update, context: ContextTypes.DEFAULT_TYPE): # Funkcja pomocnicza do escape'owania znaków specjalnych MarkdownV2 def escape_markdown_v2(text: str) -> str: """Ucieka znaki specjalne dla parsowania Telegram MarkdownV2.""" - escape_chars = r'_*[]()~`>#+-=|{}.!' - return re.sub(f'([{re.escape(escape_chars)}])', r'\\\1', text) + if not text: + return "" + + # Pełna lista znaków specjalnych w Telegram MarkdownV2 + escape_chars = r'-' + + # Zamieniamy każdy znak specjalny na jego wersję z dwoma backslashami przed nim + return re.sub(f'([{re.escape(escape_chars)}])', r'\1', text) + +# Ulepszona funkcja do bezpiecznego wysyłania wiadomości +async def safe_send_message(bot, chat_id, text, parse_mode=None, disable_web_page_preview=False, max_retries=3): + """ + Bezpiecznie wysyła wiadomość, obsługując błędy związane z formatowaniem. + + Args: + bot: Instancja bota Telegram + chat_id: ID czatu + text: Tekst do wysłania + parse_mode: Tryb parsowania (None, HTML, Markdown, MarkdownV2) + disable_web_page_preview: Czy wyłączyć podgląd linków + max_retries: Maksymalna liczba prób + + Returns: + Obiekt wysłanej wiadomości lub None w przypadku błędu + """ + # Próbuj wysłać z formatowaniem + for attempt in range(max_retries): + try: + return await bot.send_message( + chat_id=chat_id, + text=text, + parse_mode=parse_mode, + disable_web_page_preview=disable_web_page_preview + ) + except Exception as e: + logger.warning(f"Błąd wysyłania wiadomości (próba {attempt+1}/{max_retries}): {str(e)}") + error_msg = str(e).lower() + + # Jeśli jest to błąd parsowania Markdown + if "parse" in error_msg and "entities" in error_msg: + # Spróbuj ponownie bez formatowania + if attempt == max_retries - 1: + logger.info("Wysyłam wiadomość bez formatowania") + try: + return await bot.send_message( + chat_id=chat_id, + text=text, + parse_mode=None, # Bez formatowania + disable_web_page_preview=disable_web_page_preview + ) + except Exception as e2: + logger.error(f"Nie udało się wysłać wiadomości nawet bez formatowania: {str(e2)}") + return None + + return None # Funkcja do dzielenia długich wiadomości async def send_long_message(bot, chat_id, text, parse_mode=None, disable_web_page_preview=False): @@ -135,7 +275,8 @@ async def send_long_message(bot, chat_id, text, parse_mode=None, disable_web_pag if len(text) <= max_length: # Jeśli wiadomość nie przekracza limitu, wyślij ją normalnie - return await bot.send_message( + return await safe_send_message( + bot, chat_id=chat_id, text=text, parse_mode=parse_mode, @@ -150,7 +291,8 @@ async def send_long_message(bot, chat_id, text, parse_mode=None, disable_web_pag # Wyślij części wiadomości for i, part in enumerate(parts): part_header = f"*Część {i+1}/{len(parts)}*\n\n" if parse_mode == ParseMode.MARKDOWN_V2 else f"Część {i+1}/{len(parts)}\n\n" - await bot.send_message( + await safe_send_message( + bot, chat_id=chat_id, text=part_header + part, parse_mode=parse_mode,