Проблема с форматированием текста при пересылке сообщений через 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 не подойдет!)
Буду благодарен за советы!