Проблема с форматированием текста при пересылке сообщений через Telethon

У меня есть бот, он отслеживает сообщения в одном канале и пересылает их в другие. Если сообщение содержит только текст, я беру его содержимое (msg.text) и отправляю через client.send_message(..., parse_mode='HTML'), предварительно обрабатывая через markdown2.markdown() и некоторые регулярки для «очистки».

Проблема: При пересылке появляются лишние символы форматирования, которых не было в оригинальном сообщении (Особенно часто это встречается при пересылке альбомов). Например, неожиданно сохраняются одиночные или двойные *, _ или схлопываются абзацы. Также пропадают пустые строки между абзацами — текст «слипается», хотя в оригинале был разбит на логические блоки. Также из-за использования костылей для очистки сообщения в некоторых местах форматирование работает некорректно.

Вот так выглядит полный код:

import asyncio
from telethon import TelegramClient, events, types
import os
from dotenv import load_dotenv
import markdown2
import re

load_dotenv()

API_ID = int(os.getenv("API_ID"))
API_HASH = os.getenv("API_HASH")
PHONE = os.getenv("PHONE")
CHANNEL_PAIRS_RAW = os.getenv("CHANNEL_PAIRS", "")
DELAY_SECONDS = float(os.getenv("DELAY_SECONDS", "1"))  # задержка между отправками сообщений


def extract_username(link_or_username):
    link_or_username = link_or_username.strip()
    if link_or_username.startswith("https://t.me/"):
        return link_or_username.replace("https://t.me/", "").strip("/")
    if link_or_username.startswith("-100") and link_or_username[1:].isdigit():
        return int(link_or_username)
    if link_or_username.lstrip("-").isdigit():
        return int(link_or_username)
    return link_or_username


def parse_channel_pairs(pairs_raw):
    pairs = []
    if not pairs_raw:
        return pairs
    for pair_str in pairs_raw.split(","):
        if ":" not in pair_str:
            continue
        src, tgt = pair_str.split(":", 1)
        src_clean = extract_username(src.strip())
        tgt_clean = extract_username(tgt.strip())
        if src_clean and tgt_clean:
            pairs.append((src_clean, tgt_clean))
    return pairs


def clean_markdown_to_html(md_text: str) -> str:
    html = markdown2.markdown(md_text or "", extras=["strike", "target-blank-links"])
    if html.startswith("<p>") and html.endswith("</p>\n"):
        html = html[3:-5].strip()
    html = re.sub(r"\*(?=[^\w<])|(?<=[^\w>])\*", "", html)
    html = re.sub(r"_(?=[^\w<])|(?<=[^\w>])_", "", html)
    html = html.replace("*", "").replace("_", "")
    html = re.sub(r"\n{3,}", "\n\n", html)
    return html

async def main():
    client = TelegramClient(
        "session",
        API_ID,
        API_HASH,
        system_version='4.16.30-vxCUSTOM',
        device_model='CustomDevice',
        app_version='1.0.0'
    )

    await client.start(
        phone=PHONE,
        code_callback=lambda: input("Введите код из Telegram: "),
        password=lambda: input("Введите 2FA-пароль: ")
    )

    print("✅ Бот запущен.")

    channel_pairs = parse_channel_pairs(CHANNEL_PAIRS_RAW)
    if not channel_pairs:
        print("❌ Не найдены пары каналов.")
        return

    for source_channel, target_channel in channel_pairs:
        # Обработка одиночных сообщений
        @client.on(events.NewMessage(chats=source_channel))
        async def new_message_handler(event, source=source_channel, target=target_channel):
            if event.message.grouped_id:
                return  # Это часть альбома, его обработает album_handler
            msg = event.message
            try:
                text_html = clean_markdown_to_html(msg.text)
                media = msg.media
                if media and not isinstance(media, types.MessageMediaWebPage):
                    await client.send_message(target, text_html, file=media, buttons=msg.buttons, parse_mode='HTML')
                else:
                    await client.send_message(target, text_html, buttons=msg.buttons, parse_mode='HTML')
                await asyncio.sleep(DELAY_SECONDS)
                print(f"[Single] {source}→{target}")
            except Exception as e:
                print(f"[Single] Ошибка {source}→{target}: {e}")

        # Обработка альбомов
        @client.on(events.Album(chats=source_channel))
        async def album_handler(event, source=source_channel, target=target_channel):
            msgs = sorted(event.messages, key=lambda m: m.id)
            files = [m.media for m in msgs if m.media]
            if not files:
                return
            print(f"[Album] Получено {len(msgs)} частей, {len(files)} медиа из {source}")
            caption_md = next((m.text for m in msgs if m.text), "")
            buttons = next((m.buttons for m in msgs if m.buttons), None)
            caption_html = clean_markdown_to_html(caption_md)
            try:
                await client.send_file(target, files, caption=caption_html, buttons=buttons, parse_mode='HTML')
                await asyncio.sleep(DELAY_SECONDS)
                print(f"[Album] Отправлено {len(files)} файлов {source}→{target}")
            except Exception as e:
                print(f"[Album] Ошибка {source}→{target}: {e}")

    print("? Начало прослушивания...")
    await client.run_until_disconnected()


if __name__ == "__main__":
    asyncio.run(main())
    

Вопрос к форумчанам: Есть ли более надёжный способ пересылки текста через Telethon без ручной обработки markdown и без искажений форматирования? Как сохранить структуру текста (абзацы, отступы, отсутствие лишней разметки) максимально близко к оригиналу? (Обычный forward_message не подойдет!)

Буду благодарен за советы!


Ответы (0 шт):