diff --git a/handlers/new_post.py b/handlers/new_post.py index a385295..91234b3 100644 --- a/handlers/new_post.py +++ b/handlers/new_post.py @@ -140,6 +140,151 @@ async def select_text(update: Update, context: ContextTypes.DEFAULT_TYPE): await update.message.reply_text('Выберите, куда отправить пост:', reply_markup=InlineKeyboardMarkup(keyboard)) return SELECT_TARGET +# async def select_target(update: Update, context: ContextTypes.DEFAULT_TYPE): +# query = update.callback_query +# if not query: +# return ConversationHandler.END +# await query.answer() + +# data = query.data +# async with AsyncSessionLocal() as session: +# chat_id = None +# markup = None +# selected_kind = None # "channel" | "group" +# selected_title = None + +# if data and data.startswith('channel_'): +# selected_kind = "channel" +# channel_id = int(data.split('_')[1]) + +# # ACL: право постинга +# me = await get_or_create_admin(session, update.effective_user.id) +# allowed = await has_scope_on_channel(session, me.id, channel_id, SCOPE_POST) +# if not allowed: +# await query.edit_message_text("У вас нет права постить в этот канал.") +# return ConversationHandler.END + +# channel = (await session.execute(sa_select(Channel).where(Channel.id == channel_id))).scalar_one_or_none() +# if not channel: +# await query.edit_message_text("Канал не найден.") +# return ConversationHandler.END +# chat_id = (channel.link or "").strip() +# selected_title = channel.name + +# # кнопки канала +# btns = (await session.execute(sa_select(Button).where(Button.channel_id == channel_id))).scalars().all() +# if btns: +# markup = InlineKeyboardMarkup( +# [[InlineKeyboardButton(str(b.name), url=str(b.url))] for b in btns] +# ) + +# elif data and data.startswith('group_'): +# selected_kind = "group" +# group_id = int(data.split('_')[1]) +# group = (await session.execute(sa_select(Group).where(Group.id == group_id))).scalar_one_or_none() +# if not group: +# await query.edit_message_text("Группа не найдена.") +# return ConversationHandler.END +# chat_id = (group.link or "").strip() +# selected_title = group.name + +# # кнопки группы +# btns = (await session.execute(sa_select(Button).where(Button.group_id == group_id))).scalars().all() +# if btns: +# markup = InlineKeyboardMarkup( +# [[InlineKeyboardButton(str(b.name), url=str(b.url))] for b in btns] +# ) + +# if not chat_id or not (chat_id.startswith('@') or chat_id.startswith('-')): +# await query.edit_message_text('Ошибка: ссылка должна быть username (@channel) или числовой ID (-100...)') +# return ConversationHandler.END + +# ud = context.user_data or {} +# text: str = ud.get("text", "") or "" +# entities: List[MessageEntity] = ud.get("entities", []) or [] + +# # санация для custom_emoji +# entities = _strip_broken_entities(entities) +# entities = _split_custom_emoji_by_utf16(text, entities) + +# # если есть клавиатура, ПРЕДПОЧИТАЕМ отправку "вручную" (а не copyMessage), +# # т.к. в некоторых кейсах клавиатура при копировании может не приклеиться +# prefer_manual_send = markup is not None + +# # определим, были ли “части медиа” +# has_media_parts = any(k in ud for k in ("photo","animation","video","document","audio","voice","sticker")) + +# try: +# if not prefer_manual_send and ud.get("src_chat_id") and ud.get("src_msg_id") and not has_media_parts: +# # 1) copyMessage без медиа и без клавиатуры — максимальная идентичность +# msg_id_obj = await context.bot.copy_message( +# chat_id=chat_id, +# from_chat_id=ud["src_chat_id"], +# message_id=ud["src_msg_id"], +# reply_markup=markup # если вдруг есть, попробуем сразу +# ) +# # страховка: если клавиатуры нет/не приклеилась — прикрутим edit_message_reply_markup +# if markup and getattr(msg_id_obj, "message_id", None): +# try: +# await context.bot.edit_message_reply_markup( +# chat_id=chat_id, +# message_id=msg_id_obj.message_id, +# reply_markup=markup +# ) +# except Exception: +# pass +# await query.edit_message_text(f'Пост отправлен{" в: " + selected_title if selected_title else "!"}') +# return ConversationHandler.END + +# # 2) fallback / manual — ВСЕГДА прикладываем reply_markup +# sent = False +# if "photo" in ud: +# await context.bot.send_photo(chat_id=chat_id, photo=ud["photo"], +# caption=(text or None), caption_entities=(entities if text else None), +# reply_markup=markup) +# sent = True +# elif "animation" in ud: +# await context.bot.send_animation(chat_id=chat_id, animation=ud["animation"], +# caption=(text or None), caption_entities=(entities if text else None), +# reply_markup=markup) +# sent = True +# elif "video" in ud: +# await context.bot.send_video(chat_id=chat_id, video=ud["video"], +# caption=(text or None), caption_entities=(entities if text else None), +# reply_markup=markup) +# sent = True +# elif "document" in ud: +# await context.bot.send_document(chat_id=chat_id, document=ud["document"], +# caption=(text or None), caption_entities=(entities if text else None), +# reply_markup=markup) +# sent = True +# elif "audio" in ud: +# await context.bot.send_audio(chat_id=chat_id, audio=ud["audio"], +# caption=(text or None), caption_entities=(entities if text else None), +# reply_markup=markup) +# sent = True +# elif "voice" in ud: +# await context.bot.send_voice(chat_id=chat_id, voice=ud["voice"], +# caption=(text or None), caption_entities=(entities if text else None), +# reply_markup=markup) +# sent = True +# elif "sticker" in ud: +# await context.bot.send_sticker(chat_id=chat_id, sticker=ud["sticker"], reply_markup=markup) +# if text: +# await context.bot.send_message(chat_id=chat_id, text=text, entities=entities) +# sent = True +# else: +# await context.bot.send_message(chat_id=chat_id, text=text, entities=entities, reply_markup=markup) +# sent = True + +# await query.edit_message_text(f'Пост отправлен{" в: " + selected_title if selected_title else "!"}' if sent else 'Ошибка: не удалось отправить сообщение.') + +# except BadRequest as e: +# await query.edit_message_text(f'Ошибка отправки поста: {e}') + +# return ConversationHandler.END + + async def select_target(update: Update, context: ContextTypes.DEFAULT_TYPE): query = update.callback_query if not query: @@ -150,14 +295,14 @@ async def select_target(update: Update, context: ContextTypes.DEFAULT_TYPE): async with AsyncSessionLocal() as session: chat_id = None markup = None + selected_title = None if data and data.startswith('channel_'): channel_id = int(data.split('_')[1]) - # ACL: проверить право постинга + # ACL me = await get_or_create_admin(session, update.effective_user.id) - allowed = await has_scope_on_channel(session, me.id, channel_id, SCOPE_POST) - if not allowed: + if not await has_scope_on_channel(session, me.id, channel_id, SCOPE_POST): await query.edit_message_text("У вас нет права постить в этот канал.") return ConversationHandler.END @@ -165,9 +310,10 @@ async def select_target(update: Update, context: ContextTypes.DEFAULT_TYPE): if not channel: await query.edit_message_text("Канал не найден.") return ConversationHandler.END - chat_id = channel.link + chat_id = (channel.link or "").strip() + selected_title = channel.name - # КНОПКИ ДЛЯ КАНАЛА + # КНОПКИ btns = (await session.execute(sa_select(Button).where(Button.channel_id == channel_id))).scalars().all() if btns: markup = InlineKeyboardMarkup([[InlineKeyboardButton(str(b.name), url=str(b.url))] for b in btns]) @@ -178,82 +324,66 @@ async def select_target(update: Update, context: ContextTypes.DEFAULT_TYPE): if not group: await query.edit_message_text("Группа не найдена.") return ConversationHandler.END - chat_id = group.link + chat_id = (group.link or "").strip() + selected_title = group.name - # КНОПКИ ДЛЯ ГРУППЫ btns = (await session.execute(sa_select(Button).where(Button.group_id == group_id))).scalars().all() if btns: markup = InlineKeyboardMarkup([[InlineKeyboardButton(str(b.name), url=str(b.url))] for b in btns]) - if not chat_id: - await query.edit_message_text('Ошибка: объект не найден.') - return ConversationHandler.END - - chat_id = chat_id.strip() - if not (chat_id.startswith('@') or chat_id.startswith('-')): - await query.edit_message_text('Ошибка: ссылка должна быть username (@channel) или числовой ID (-100...)') + if not chat_id or not (chat_id.startswith('@') or chat_id.startswith('-')): + await query.edit_message_text('Ошибка: ссылка должна быть @username или -100...') return ConversationHandler.END ud = context.user_data or {} text: str = ud.get("text", "") or "" entities: List[MessageEntity] = ud.get("entities", []) or [] - # санация перед отправкой (custom_emoji/UTF-16) + # санация custom_emoji/UTF-16 entities = _strip_broken_entities(entities) entities = _split_custom_emoji_by_utf16(text, entities) - # 1) Пытаемся «бит-в-бит» копию + КЛАВИАТУРУ - has_media_parts = any(k in ud for k in ("photo","animation","video","document","audio","voice","sticker")) - try: - if ud.get("src_chat_id") and ud.get("src_msg_id") and not has_media_parts: - await context.bot.copy_message( - chat_id=chat_id, - from_chat_id=ud["src_chat_id"], - message_id=ud["src_msg_id"], - reply_markup=markup, # <— ВАЖНО - ) - await query.edit_message_text("Пост отправлен!") - return ConversationHandler.END - except BadRequest: - pass # упадём в fallback - - # 2) fallback — отправка с entities/caption_entities и КЛАВИАТУРОЙ + # >>> ВАЖНО: НИКАКОГО copyMessage. Всегда manual send с reply_markup <<< try: sent = False if "photo" in ud: await context.bot.send_photo(chat_id=chat_id, photo=ud["photo"], - caption=(text or None), caption_entities=(entities if text else None), + caption=(text or None), + caption_entities=(entities if text else None), reply_markup=markup) sent = True elif "animation" in ud: await context.bot.send_animation(chat_id=chat_id, animation=ud["animation"], - caption=(text or None), caption_entities=(entities if text else None), + caption=(text or None), + caption_entities=(entities if text else None), reply_markup=markup) sent = True elif "video" in ud: await context.bot.send_video(chat_id=chat_id, video=ud["video"], - caption=(text or None), caption_entities=(entities if text else None), + caption=(text or None), + caption_entities=(entities if text else None), reply_markup=markup) sent = True elif "document" in ud: await context.bot.send_document(chat_id=chat_id, document=ud["document"], - caption=(text or None), caption_entities=(entities if text else None), + caption=(text or None), + caption_entities=(entities if text else None), reply_markup=markup) sent = True elif "audio" in ud: await context.bot.send_audio(chat_id=chat_id, audio=ud["audio"], - caption=(text or None), caption_entities=(entities if text else None), + caption=(text or None), + caption_entities=(entities if text else None), reply_markup=markup) sent = True elif "voice" in ud: await context.bot.send_voice(chat_id=chat_id, voice=ud["voice"], - caption=(text or None), caption_entities=(entities if text else None), + caption=(text or None), + caption_entities=(entities if text else None), reply_markup=markup) sent = True elif "sticker" in ud: - # Можно прикрепить клавиатуру и к стикеру (Telegram поддерживает reply_markup) await context.bot.send_sticker(chat_id=chat_id, sticker=ud["sticker"], reply_markup=markup) - # Если есть текст — отправим вторым сообщением (кнопки уже в первом) if text: await context.bot.send_message(chat_id=chat_id, text=text, entities=entities) sent = True @@ -261,13 +391,14 @@ async def select_target(update: Update, context: ContextTypes.DEFAULT_TYPE): await context.bot.send_message(chat_id=chat_id, text=text, entities=entities, reply_markup=markup) sent = True - await query.edit_message_text('Пост отправлен!' if sent else 'Ошибка: не удалось отправить сообщение.') + await query.edit_message_text(f'Пост отправлен{(" в: " + selected_title) if selected_title else "!"}' if sent else 'Ошибка: не удалось отправить сообщение.') except BadRequest as e: await query.edit_message_text(f'Ошибка отправки поста: {e}') return ConversationHandler.END + new_post_conv = ConversationHandler( entry_points=[CommandHandler("new_post", new_post_start)], states={