21
This commit is contained in:
@@ -34,17 +34,13 @@ HELP_TEXT = (
|
||||
"/help - показать справку\n"
|
||||
"/refresh - перерисовать пост в канале\n"
|
||||
"/cancel - сбросить текущий ввод\n"
|
||||
"/post - сохранить шаблон поста с плейсхолдером {{actors}}\n\n"
|
||||
"Как пользоваться\n"
|
||||
"1. Откройте панель.\n"
|
||||
"2. Выберите своего актера.\n"
|
||||
"3. Нажмите один из статусов. Он применится сразу с шаблонной фразой.\n"
|
||||
"4. Если нужен свой текст, нажмите 'Своя фраза' и отправьте его.\n\n"
|
||||
"/post - сохранить шаблон поста\n"
|
||||
"/template_dump - показать HTML из сообщения-реплая\n\n"
|
||||
"Шаблон поста\n"
|
||||
"В шаблоне должен быть {{actors}} - туда бот подставляет актерский блок.\n"
|
||||
"Можно добавить {{hidden_link}} в начало, либо указать hidden_link_url в config/actors.json.\n"
|
||||
"Шаблон лучше отправлять обычным текстом или реплаем на текстовый пост.\n"
|
||||
"Точное восстановление исходного MarkdownV2 из пересланного оформленного поста Telegram не гарантируется."
|
||||
"Можно использовать один общий {{actors}} для всего блока актеров.\n"
|
||||
"Или точечные плейсхолдеры: {{actor:liebe}}, {{actor:mari}}.\n"
|
||||
"Можно добавить {{hidden_link}} в начало, либо задать HIDDEN_LINK_URL в .env.\n"
|
||||
"Если нужна готовая Telegram-разметка, используйте /post ответом на уже оформленное сообщение."
|
||||
)
|
||||
|
||||
|
||||
@@ -90,6 +86,12 @@ def build_status_keyboard(actor_key: str) -> InlineKeyboardBuilder:
|
||||
return keyboard
|
||||
|
||||
|
||||
def build_back_to_actor_keyboard(actor_key: str) -> InlineKeyboardBuilder:
|
||||
keyboard = InlineKeyboardBuilder()
|
||||
keyboard.button(text="⬅️ Назад", callback_data=f"actor:{actor_key}")
|
||||
return keyboard
|
||||
|
||||
|
||||
def build_back_to_panel_keyboard() -> InlineKeyboardBuilder:
|
||||
keyboard = InlineKeyboardBuilder()
|
||||
keyboard.button(text="К панели", callback_data="nav:panel")
|
||||
@@ -122,15 +124,34 @@ def normalize_template_placeholders(template: str) -> str:
|
||||
normalized = re.sub(r"\\\{\\\{\s*actors\s*\\\}\\\}", "{{actors}}", normalized, flags=re.IGNORECASE)
|
||||
normalized = re.sub(r"\{\{\s*actors\s*\}\}", "{{actors}}", normalized, flags=re.IGNORECASE)
|
||||
normalized = re.sub(
|
||||
r"\\\{\\\{\s*hidden_link\s*\\\}\\\}",
|
||||
"{{hidden_link}}",
|
||||
r"\\\{\\\{\s*actor\s*:\s*([a-z0-9_\-]+)\s*\\\}\\\}",
|
||||
lambda match: f"{{{{actor:{match.group(1).lower()}}}}}",
|
||||
normalized,
|
||||
flags=re.IGNORECASE,
|
||||
)
|
||||
normalized = re.sub(
|
||||
r"\{\{\s*actor\s*:\s*([a-z0-9_\-]+)\s*\}\}",
|
||||
lambda match: f"{{{{actor:{match.group(1).lower()}}}}}",
|
||||
normalized,
|
||||
flags=re.IGNORECASE,
|
||||
)
|
||||
normalized = re.sub(r"\\\{\\\{\s*hidden_link\s*\\\}\\\}", "{{hidden_link}}", normalized, flags=re.IGNORECASE)
|
||||
normalized = re.sub(r"\{\{\s*hidden_link\s*\}\}", "{{hidden_link}}", normalized, flags=re.IGNORECASE)
|
||||
return normalized
|
||||
|
||||
|
||||
def validate_template_structure(template: str) -> str | None:
|
||||
normalized = normalize_template_placeholders(template)
|
||||
common_count = normalized.count("{{actors}}")
|
||||
specific_count = len(re.findall(r"\{\{actor:[a-z0-9_\-]+\}\}", normalized, flags=re.IGNORECASE))
|
||||
|
||||
if common_count == 0 and specific_count == 0:
|
||||
return "Шаблон сохранен, но в нем нет {{actors}} или {{actor:key}}."
|
||||
if common_count > 1:
|
||||
return "Шаблон сохранен, но в нем несколько {{actors}}. Нужен только один общий {{actors}}."
|
||||
return None
|
||||
|
||||
|
||||
async def safe_edit_message(callback: CallbackQuery, text: str, reply_markup=None) -> None:
|
||||
try:
|
||||
await callback.message.edit_text(
|
||||
@@ -155,11 +176,7 @@ async def show_panel(target: Message | CallbackQuery, user_id: int, app_config:
|
||||
await target.answer(text, reply_markup=keyboard.as_markup())
|
||||
|
||||
|
||||
async def show_actor_status_menu(
|
||||
callback: CallbackQuery,
|
||||
actor: dict[str, Any],
|
||||
state_storage: JsonStateStorage,
|
||||
) -> None:
|
||||
async def show_actor_status_menu(callback: CallbackQuery, actor: dict[str, Any], state_storage: JsonStateStorage) -> None:
|
||||
runtime_state = get_actor_runtime_state(actor, state_storage)
|
||||
current_status = runtime_state.get("status", actor.get("default_status", "backstage"))
|
||||
current_phrase = runtime_state.get("phrase", actor.get("phrases", {}).get(current_status, ""))
|
||||
@@ -213,26 +230,20 @@ def save_post_template(state_storage: JsonStateStorage, template: str) -> None:
|
||||
state_storage.save(payload)
|
||||
|
||||
|
||||
def count_actor_placeholders(template: str) -> int:
|
||||
return normalize_template_placeholders(template).count("{{actors}}")
|
||||
|
||||
|
||||
@router.message(CommandStart())
|
||||
@router.message(Command("panel"))
|
||||
async def start_handler(message: Message, state: FSMContext, app_config: dict, actor_lookup: dict, settings) -> None:
|
||||
await state.clear()
|
||||
user_id = message.from_user.id
|
||||
|
||||
if not is_allowed(user_id, actor_lookup, settings.admin_ids):
|
||||
await message.answer("У вас нет доступа к этой панели.")
|
||||
return
|
||||
|
||||
await show_panel(message, user_id, app_config, settings)
|
||||
|
||||
|
||||
@router.message(Command("help"))
|
||||
async def help_handler(message: Message) -> None:
|
||||
await message.answer(HELP_TEXT, disable_web_page_preview=True)
|
||||
await message.answer(HELP_TEXT)
|
||||
|
||||
|
||||
@router.message(Command("cancel"))
|
||||
@@ -263,6 +274,22 @@ async def refresh_handler(
|
||||
await message.answer("Сообщение канала обновлено.")
|
||||
|
||||
|
||||
@router.message(Command("template_dump"))
|
||||
async def template_dump_handler(message: Message, actor_lookup: dict, settings) -> None:
|
||||
user_id = message.from_user.id
|
||||
if not is_allowed(user_id, actor_lookup, settings.admin_ids):
|
||||
await message.answer("У вас нет доступа к шаблонам поста.")
|
||||
return
|
||||
if message.reply_to_message is None:
|
||||
await message.answer("Используйте /template_dump ответом на оформленное сообщение.")
|
||||
return
|
||||
source = message.reply_to_message
|
||||
if source.html_text:
|
||||
await message.answer(source.html_text)
|
||||
return
|
||||
await message.answer("У сообщения нет HTML-представления, которое можно извлечь.")
|
||||
|
||||
|
||||
@router.message(Command("post"))
|
||||
async def post_handler(
|
||||
message: Message,
|
||||
@@ -281,32 +308,26 @@ async def post_handler(
|
||||
normalized = normalize_template_placeholders(template)
|
||||
save_post_template(state_storage, normalized)
|
||||
await state.clear()
|
||||
placeholder_count = count_actor_placeholders(normalized)
|
||||
if placeholder_count == 0:
|
||||
await message.answer(
|
||||
"Шаблон сохранен, но в нем нет {{actors}}.\n"
|
||||
"Плейсхолдер должен быть именно {{actors}} в любом регистре."
|
||||
)
|
||||
return
|
||||
if placeholder_count > 1:
|
||||
await message.answer(
|
||||
"Шаблон сохранен, но в нем несколько {{actors}}.\n"
|
||||
"Нужен только один общий {{actors}} на месте всего блока актеров, иначе список будет дублироваться."
|
||||
)
|
||||
|
||||
validation_error = validate_template_structure(normalized)
|
||||
if validation_error:
|
||||
await message.answer(validation_error)
|
||||
return
|
||||
|
||||
if message.text and message.text.startswith("/post "):
|
||||
await message.answer(
|
||||
"Шаблон сохранен.\n"
|
||||
"Но если нужна разметка, ссылки и premium emoji, лучше использовать /post ответом на уже оформленное сообщение, а не вставлять шаблон текстом после команды."
|
||||
"Но если нужна готовая разметка, ссылки и premium emoji, лучше использовать /post ответом на уже оформленное сообщение."
|
||||
)
|
||||
return
|
||||
|
||||
await message.answer("Шаблон поста сохранен.")
|
||||
return
|
||||
|
||||
await state.set_state(SessionForm.waiting_for_post_template)
|
||||
await message.answer(
|
||||
"Перешлите или отправьте текст шаблона одним сообщением.\n"
|
||||
"Внутри должен быть {{actors}}.\n"
|
||||
"Перешлите или отправьте шаблон одним сообщением.\n"
|
||||
"Используйте {{actors}} для общего блока или {{actor:key}} для конкретного актера.\n"
|
||||
"Для скрытой ссылки можно использовать {{hidden_link}}."
|
||||
)
|
||||
|
||||
@@ -436,10 +457,9 @@ async def custom_phrase_handler(
|
||||
callback,
|
||||
(
|
||||
f"Введите свою фразу для {actor['display_name']}.\n"
|
||||
f"Текущий статус останется: {STATUS_CHOICES.get(status_key, status_key)}.\n"
|
||||
"Если хотите сначала сменить статус, вернитесь назад."
|
||||
f"Текущий статус останется: {STATUS_CHOICES.get(status_key, status_key)}."
|
||||
),
|
||||
build_status_keyboard(actor_key).as_markup(),
|
||||
build_back_to_actor_keyboard(actor_key).as_markup(),
|
||||
)
|
||||
await callback.answer()
|
||||
|
||||
@@ -510,18 +530,9 @@ async def post_template_handler(
|
||||
save_post_template(state_storage, normalized)
|
||||
await state.clear()
|
||||
|
||||
placeholder_count = count_actor_placeholders(normalized)
|
||||
if placeholder_count == 0:
|
||||
await message.answer(
|
||||
"Шаблон сохранен, но в нем нет {{actors}}.\n"
|
||||
"Плейсхолдер должен быть именно {{actors}} в любом регистре."
|
||||
)
|
||||
return
|
||||
if placeholder_count > 1:
|
||||
await message.answer(
|
||||
"Шаблон сохранен, но в нем несколько {{actors}}.\n"
|
||||
"Нужен только один общий {{actors}} на месте всего блока актеров, иначе список будет дублироваться."
|
||||
)
|
||||
validation_error = validate_template_structure(normalized)
|
||||
if validation_error:
|
||||
await message.answer(validation_error)
|
||||
return
|
||||
|
||||
await message.answer("Шаблон поста сохранен.")
|
||||
@@ -541,6 +552,10 @@ async def main() -> None:
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
settings = load_settings()
|
||||
app_config = load_actor_config(settings.config_path)
|
||||
if settings.hidden_link_url:
|
||||
app_config["hidden_link_url"] = settings.hidden_link_url
|
||||
if settings.hidden_link_char:
|
||||
app_config["hidden_link_char"] = settings.hidden_link_char
|
||||
state_storage = JsonStateStorage(settings.state_path)
|
||||
|
||||
bot = Bot(token=settings.bot_token)
|
||||
|
||||
Reference in New Issue
Block a user