125 lines
8.3 KiB
Python
125 lines
8.3 KiB
Python
from __future__ import annotations
|
||
|
||
import html
|
||
|
||
WELCOME_RICH_HTML = """<h1>Привет! Я — бот для написания постов</h1>
|
||
<p>Помогаю писать и редактировать посты для соцсетей. Расскажи, о чём нужен пост, а я верну три варианта: короткий, развёрнутый и с призывом к действию.</p>
|
||
<h2>Примеры запросов</h2>
|
||
<ul>
|
||
<li>«Напиши пост про запуск нового продукта»</li>
|
||
<li>«Сделай пост-напоминание о вебинаре в официальном тоне»</li>
|
||
<li>«Обнови этот вариант, добавь эмодзи» — реплаем на мой текст</li>
|
||
</ul>
|
||
<p>Выбери режим в нижнем меню: «Обучение», «Генерация», «Подписка» или «Помощь». Внутри режима кнопка «Главное меню» возвращает сюда. Команда /start тоже открывает главное меню.</p>
|
||
<p>В режиме «Обучение» пришли скриншоты своих постов; в режиме «Генерация» попроси идею или пост.</p>
|
||
"""
|
||
|
||
HELP_MENU_TITLE = "Помощь по боту"
|
||
HELP_MENU_INTRO = "Выбери раздел — откроется только он. Из любого раздела можно перейти в любой."
|
||
HELP_SECTIONS: dict[str, tuple[str, str]] = {
|
||
"posts": (
|
||
"✍️ Написать пост",
|
||
"""<ol>
|
||
<li>В режиме «Генерация» напиши сообщение или отправь голосовое с прямой просьбой, например: «Придумай пост для Telegram про запуск нового продукта».</li>
|
||
<li>Можешь указать площадку, аудиторию, факты, длину, цель и другие требования. Можно надиктовать подробное ТЗ или просто тему.</li>
|
||
<li>Я распознаю речь, проверю, просишь ли ты именно написать пост, и верну три варианта отдельными сообщениями.</li>
|
||
</ol>
|
||
<p>В сообщениях с вариантами будет только текст поста: без нумерации и вступлений, с абзацами, переносами строк и уместными эмодзи.</p>""",
|
||
),
|
||
"tone": (
|
||
"🎨 Tone of Voice",
|
||
"""<p>Пришли скриншоты постов, текстовые примеры или просто расскажи об аккаунте. Можно отправить голосовое или аудиофайл. Я буду постепенно накапливать и обновлять профиль аккаунта и Tone of Voice; новые сведения дополняют прежние.</p>
|
||
<p>Чтобы начать обучение заново и удалить накопленное, нажми «Сбросить данные» и подтверди действие.</p>""",
|
||
),
|
||
"edit": (
|
||
"✏️ Правка поста",
|
||
"""<p>Ответь реплаем на сообщение с вариантом и укажи, что изменить. Например: «Сделай короче и добавь призыв». Я учту оригинальный текст и верну три новых варианта отдельными сообщениями.</p>""",
|
||
),
|
||
"idea": (
|
||
"💡 Идея для поста",
|
||
"""<p>Напиши «предложи идею для поста», «о чём написать?» или «накидай тему» — верну одну идею. Команда <code>/idea</code> работает так же.</p>
|
||
<p>Идеи сохраняются, чтобы не повторяться: каждая новая идея отличается от предыдущих.</p>""",
|
||
),
|
||
"subscription": (
|
||
"💳 Подписка",
|
||
"""<p>У тебя есть пробный период — 3 дня бесплатно. Дальше — подписка на 30 дней за 100 звёзд.</p>
|
||
<ul>
|
||
<li><code>/balance</code> — узнать статус: сколько осталось дней триала или подписки.</li>
|
||
<li><code>/subscribe</code> — оплатить подписку на 30 дней.</li>
|
||
</ul>
|
||
<p>При входе в режим «Подписка» бот сразу показывает активный период и сколько дней осталось. Нажми «Продлить подписку», чтобы сразу открыть счёт в звёздах. Когда период закончится, генерация постов и идей станет недоступна.</p>""",
|
||
),
|
||
"commands": (
|
||
"⌨️ Команды",
|
||
"""<p>Команды-переключатели доступны в любом режиме: <code>/start</code> — главное меню; <code>/training</code> — обучение; <code>/generate</code> — генерация; <code>/subscription</code> — подписка; <code>/help</code> — помощь.</p>
|
||
<p>Действия доступны только в своём режиме: <code>/style</code> и <code>/clear</code> — обучение; <code>/post</code> и <code>/idea</code> — генерация; <code>/balance</code> и <code>/subscribe</code> — подписка. <code>/clear</code> и кнопка «Сбросить данные» сначала запрашивают подтверждение; подписка и триал не меняются.</p>
|
||
<p>В режиме «Генерация» можно попросить идею обычными словами: «предложи идею для поста», «о чём написать?», «накидай тему».</p>""",
|
||
),
|
||
}
|
||
|
||
HELP_SECTION_ORDER: tuple[str, ...] = tuple(HELP_SECTIONS)
|
||
HELP_MENU_BUTTON = "⌂ В меню"
|
||
|
||
|
||
def _menu_buttons(current: str | None = None) -> str:
|
||
rows = []
|
||
for section in HELP_SECTION_ORDER:
|
||
title = HELP_SECTIONS[section][0]
|
||
if section == current:
|
||
button = f'<tg-button type="disabled">{title}</tg-button>'
|
||
else:
|
||
button = (
|
||
f'<tg-button type="callback_data" data="help:{section}" style="primary">'
|
||
f"{title}</tg-button>"
|
||
)
|
||
rows.append(f'<tg-button-row align="left">{button}</tg-button-row>')
|
||
return "".join(rows)
|
||
|
||
|
||
def _menu_button_row() -> str:
|
||
return (
|
||
f'<tg-button-row align="left">'
|
||
f'<tg-button type="callback_data" data="help:menu" style="primary">'
|
||
f"{HELP_MENU_BUTTON}</tg-button></tg-button-row>"
|
||
)
|
||
|
||
|
||
def help_menu_html(current: str | None = None) -> str:
|
||
return (
|
||
f"<h1>{HELP_MENU_TITLE}</h1>"
|
||
f"<p>{HELP_MENU_INTRO}</p>"
|
||
f"<p>Я помогаю писать и редактировать посты для соцсетей. "
|
||
f"Генерирую посты только по явной просьбе.</p>"
|
||
f"{_menu_buttons(current)}"
|
||
)
|
||
|
||
|
||
def help_section_html(section: str) -> str:
|
||
title, body = HELP_SECTIONS[section]
|
||
return f"<h1>{title}</h1>{body}{_menu_button_row()}{_menu_buttons(section)}"
|
||
|
||
|
||
def help_view(section: str | None = None) -> str:
|
||
if section and section in HELP_SECTIONS:
|
||
return help_section_html(section)
|
||
return help_menu_html(section)
|
||
|
||
|
||
def help_plain_view(section: str | None = None) -> str:
|
||
if section and section in HELP_SECTIONS:
|
||
title, body = HELP_SECTIONS[section]
|
||
return f"{HELP_MENU_TITLE} — {title}\n\n{rich_to_plain(body)}"
|
||
lines = [HELP_MENU_TITLE, HELP_MENU_INTRO]
|
||
lines += [f"• {HELP_SECTIONS[name][0]}" for name in HELP_SECTION_ORDER]
|
||
return "\n".join(lines)
|
||
|
||
|
||
def rich_to_plain(rich_html: str) -> str:
|
||
import re
|
||
|
||
text = re.sub(r"<li(?:\s[^>]*)?>", "• ", rich_html, flags=re.IGNORECASE)
|
||
text = re.sub(r"</?(?:h[1-6]|p|ul|ol|li|hr|table|tr|td|th|input)(?:\s[^>]*)?/?>", "\n", text, flags=re.IGNORECASE)
|
||
text = re.sub(r"<[^>]+>", "", text)
|
||
text = html.unescape(text)
|
||
return "\n".join(line.strip() for line in text.splitlines() if line.strip())
|