bot rafactor and bugfix

This commit is contained in:
2025-08-19 04:45:16 +09:00
parent 43dda889f8
commit a8d860ed87
31 changed files with 4396 additions and 613 deletions

View File

@@ -1,31 +1,111 @@
from __future__ import annotations
import shlex
from typing import Dict
from enum import Enum
from typing import Dict, Optional
class MessageType(Enum):
TEXT = "text"
PHOTO = "photo"
VIDEO = "video"
ANIMATION = "animation"
class Messages:
# Команды
WELCOME_MESSAGE = (
"👋 Привет! Я редактор постов для каналов.\n\n"
"🤖 Сначала добавьте бота через /add_bot\n"
"📢 Затем добавьте каналы через /add_channel\n"
"📝 Потом можно:\n"
"- Создать пост: /newpost\n"
"- Управлять шаблонами: /tpl_new, /tpl_list\n"
"- Посмотреть список ботов: /bots\n"
"- Посмотреть список каналов: /channels\n\n"
"❓ Справка: /help"
)
HELP_MESSAGE = (
"📖 Справка по командам:\n\n"
"Управление ботами и каналами:\n"
"/add_bot - Добавить нового бота\n"
"/bots - Список ваших ботов\n"
"/add_channel - Добавить канал\n"
"/channels - Список ваших каналов\n\n"
"Управление постами:\n"
"/newpost - Создать новый пост\n\n"
"Управление шаблонами:\n"
"/tpl_new - Создать шаблон\n"
"/tpl_list - Список ваших шаблонов"
)
START = ("👋 Привет! Я редактор постов. Доступные команды:\n"
"/newpost — создать новый пост\n"
"/tpl_new — создать шаблон\n"
"/tpl_list — список шаблонов")
# Ошибки
ERROR_SESSION_EXPIRED = "❌ Сессия истекла. Начните заново с /newpost"
ERROR_INVALID_FORMAT = "❌ Неверный формат. Попробуйте еще раз"
ERROR_NO_CHANNELS = "У вас нет каналов. Добавьте канал через админку"
ERROR_TEMPLATE_NOT_FOUND = "❌ Шаблон не найден"
ERROR_TEMPLATE_CREATE = "❌ Ошибка при создании шаблона: {error}"
ERROR_INVALID_KEYBOARD = "❌ Неверный формат клавиатуры"
# Создание поста
SELECT_CHANNEL = "📢 Выберите канал для публикации:"
SELECT_TYPE = "📝 Выберите тип поста:"
SELECT_FORMAT = "🔤 Выберите формат текста:"
ENTER_TEXT = "✏️ Введите текст сообщения\nИли используйте #имя_шаблона для применения шаблона"
ENTER_MEDIA = "📎 Отправьте {media_type}"
ENTER_KEYBOARD = "⌨️ Введите клавиатуру в формате:\nтекст|url\nтекст2|url2\n\nИли отправьте 'skip' для пропуска"
# Шаблоны
TEMPLATE_LIST = "📜 Список шаблонов (стр. {page}/{total_pages}):"
TEMPLATE_CREATE_NAME = "📝 Введите имя для нового шаблона:"
TEMPLATE_CREATE_TYPE = "📌 Выберите тип шаблона:"
TEMPLATE_CREATE_FORMAT = "🔤 Выберите формат текста:"
TEMPLATE_CREATE_CONTENT = "✏️ Введите содержимое шаблона:"
TEMPLATE_CREATE_KEYBOARD = "⌨️ Введите клавиатуру или отправьте 'skip':"
TEMPLATE_CREATED = "✅ Шаблон успешно создан"
TEMPLATE_DELETED = "🗑 Шаблон удален"
# Отправка
CONFIRM_SEND = "📤 Как отправить пост?"
ENTER_SCHEDULE = "📅 Введите дату и время для публикации в формате YYYY-MM-DD HH:MM"
POST_SCHEDULED = "✅ Пост запланирован на {datetime}"
POST_SENT = "✅ Пост отправлен"
class MessageParsers:
@staticmethod
def parse_template_invocation(s: str) -> tuple[str, Dict[str, str]]:
"""
Пример: "#promo title='Hi' url=https://x.y"
-> ("promo", {"title":"Hi", "url":"https://x.y"})
Парсит вызов шаблона вида: "#promo title='Hi' url=https://x.y"
Возвращает: ("promo", {"title":"Hi", "url":"https://x.y"})
"""
s = (s or "").strip()
if not s.startswith("#"):
raise ValueError("not a template invocation")
parts = shlex.split(s)
name = parts[0][1:]
args: Dict[str, str] = {}
for tok in parts[1:]:
if "=" in tok:
k, v = tok.split("=", 1)
args[k] = v
return name, args
try:
parts = shlex.split(s)
name = parts[0][1:] # убираем #
args: Dict[str, str] = {}
for tok in parts[1:]:
if "=" in tok:
k, v = tok.split("=", 1)
args[k.strip()] = v.strip().strip('"\'')
return name, args
except ValueError as e:
raise ValueError(f"Ошибка парсинга шаблона: {e}")
@staticmethod
def parse_key_value_lines(text: str) -> Dict[str, str]:
"""
Поддерживает:
Парсит переменные в форматах:
- построчно:
key=value
key2="quoted value"
@@ -35,17 +115,60 @@ class MessageParsers:
text = (text or "").strip()
if not text:
return {}
if "\n" in text:
out: Dict[str, str] = {}
try:
if "\n" in text:
# Построчный формат
out: Dict[str, str] = {}
for line in text.splitlines():
line = line.strip()
if "=" in line:
k, v = line.split("=", 1)
out[k.strip()] = v.strip().strip('"\'')
return out
else:
# Однострочный формат
out: Dict[str, str] = {}
for tok in shlex.split(text):
if "=" in tok:
k, v = tok.split("=", 1)
out[k.strip()] = v.strip()
return out
except ValueError as e:
raise ValueError(f"Ошибка парсинга переменных: {e}")
@staticmethod
def parse_keyboard(text: str) -> Optional[Dict]:
"""
Парсит клавиатуру в формате:
текст1|url1
текст2|url2
Возвращает:
{
"rows": [
[{"text": "текст1", "url": "url1"}],
[{"text": "текст2", "url": "url2"}]
]
}
"""
text = (text or "").strip()
if not text or text.lower() == "skip":
return None
try:
rows = []
for line in text.splitlines():
if "=" in line:
k, v = line.split("=", 1)
out[k.strip()] = v.strip().strip('"')
return out
out: Dict[str, str] = {}
for tok in shlex.split(text):
if "=" in tok:
k, v = tok.split("=", 1)
out[k] = v
line = line.strip()
if "|" in line:
text, url = line.split("|", 1)
rows.append([{
"text": text.strip(),
"url": url.strip()
}])
return {"rows": rows} if rows else None
except Exception as e:
raise ValueError(f"Ошибка парсинга клавиатуры: {e}")
return out