Broadcast HTML fix and subscription keyboards improvements
This commit is contained in:
+113
-88
@@ -11,16 +11,23 @@ from config.settings import Settings
|
||||
from db.dal import user_dal, message_log_dal
|
||||
|
||||
from bot.states.admin_states import AdminStates
|
||||
from bot.keyboards.inline.admin_keyboards import get_broadcast_confirmation_keyboard, get_back_to_admin_panel_keyboard, get_admin_panel_keyboard
|
||||
from bot.keyboards.inline.admin_keyboards import (
|
||||
get_broadcast_confirmation_keyboard,
|
||||
get_back_to_admin_panel_keyboard,
|
||||
get_admin_panel_keyboard,
|
||||
)
|
||||
from bot.middlewares.i18n import JsonI18n
|
||||
|
||||
router = Router(name="admin_broadcast_router")
|
||||
|
||||
|
||||
async def broadcast_message_prompt_handler(callback: types.CallbackQuery,
|
||||
state: FSMContext, i18n_data: dict,
|
||||
settings: Settings,
|
||||
session: AsyncSession):
|
||||
async def broadcast_message_prompt_handler(
|
||||
callback: types.CallbackQuery,
|
||||
state: FSMContext,
|
||||
i18n_data: dict,
|
||||
settings: Settings,
|
||||
session: AsyncSession,
|
||||
):
|
||||
current_lang = i18n_data.get("current_language", settings.DEFAULT_LANGUAGE)
|
||||
i18n: Optional[JsonI18n] = i18n_data.get("i18n_instance")
|
||||
if not i18n:
|
||||
@@ -35,25 +42,28 @@ async def broadcast_message_prompt_handler(callback: types.CallbackQuery,
|
||||
try:
|
||||
await callback.message.edit_text(
|
||||
prompt_text,
|
||||
reply_markup=get_back_to_admin_panel_keyboard(
|
||||
current_lang, i18n))
|
||||
reply_markup=get_back_to_admin_panel_keyboard(current_lang, i18n),
|
||||
)
|
||||
except Exception as e:
|
||||
logging.warning(
|
||||
f"Could not edit message for broadcast prompt: {e}. Sending new."
|
||||
)
|
||||
await callback.message.answer(
|
||||
prompt_text,
|
||||
reply_markup=get_back_to_admin_panel_keyboard(
|
||||
current_lang, i18n))
|
||||
reply_markup=get_back_to_admin_panel_keyboard(current_lang, i18n),
|
||||
)
|
||||
await callback.answer()
|
||||
await state.set_state(AdminStates.waiting_for_broadcast_message)
|
||||
|
||||
|
||||
@router.message(AdminStates.waiting_for_broadcast_message, F.text)
|
||||
async def process_broadcast_message_handler(message: types.Message,
|
||||
state: FSMContext, i18n_data: dict,
|
||||
settings: Settings,
|
||||
session: AsyncSession):
|
||||
async def process_broadcast_message_handler(
|
||||
message: types.Message,
|
||||
state: FSMContext,
|
||||
i18n_data: dict,
|
||||
settings: Settings,
|
||||
session: AsyncSession,
|
||||
):
|
||||
current_lang = i18n_data.get("current_language", settings.DEFAULT_LANGUAGE)
|
||||
i18n: Optional[JsonI18n] = i18n_data.get("i18n_instance")
|
||||
if not i18n:
|
||||
@@ -63,28 +73,35 @@ async def process_broadcast_message_handler(message: types.Message,
|
||||
|
||||
_ = lambda key, **kwargs: i18n.gettext(current_lang, key, **kwargs)
|
||||
|
||||
broadcast_message_text = message.html_text
|
||||
await state.update_data(broadcast_message=broadcast_message_text)
|
||||
# Сохраняем в state исходный текст и entities
|
||||
text = message.text or message.caption or ""
|
||||
entities = message.entities or message.caption_entities or []
|
||||
|
||||
preview_snippet = (
|
||||
broadcast_message_text[:200] +
|
||||
"...") if len(broadcast_message_text) > 200 else broadcast_message_text
|
||||
confirmation_prompt = _("admin_broadcast_confirm_prompt",
|
||||
message_preview=preview_snippet)
|
||||
await state.update_data(
|
||||
broadcast_text=text,
|
||||
broadcast_entities=entities,
|
||||
)
|
||||
|
||||
await message.answer(confirmation_prompt,
|
||||
reply_markup=get_broadcast_confirmation_keyboard(
|
||||
current_lang, i18n),
|
||||
parse_mode="HTML")
|
||||
preview_snippet = (text[:200] + "...") if len(text) > 200 else text
|
||||
confirmation_prompt = _("admin_broadcast_confirm_prompt", message_preview=preview_snippet)
|
||||
|
||||
await message.answer(
|
||||
confirmation_prompt,
|
||||
reply_markup=get_broadcast_confirmation_keyboard(current_lang, i18n),
|
||||
)
|
||||
await state.set_state(AdminStates.confirming_broadcast)
|
||||
|
||||
|
||||
@router.callback_query(F.data == "admin_action:main",
|
||||
AdminStates.waiting_for_broadcast_message)
|
||||
async def cancel_broadcast_at_prompt_stage(callback: types.CallbackQuery,
|
||||
state: FSMContext,
|
||||
settings: Settings, i18n_data: dict,
|
||||
session: AsyncSession):
|
||||
@router.callback_query(
|
||||
F.data == "admin_action:main", AdminStates.waiting_for_broadcast_message
|
||||
)
|
||||
async def cancel_broadcast_at_prompt_stage(
|
||||
callback: types.CallbackQuery,
|
||||
state: FSMContext,
|
||||
settings: Settings,
|
||||
i18n_data: dict,
|
||||
session: AsyncSession,
|
||||
):
|
||||
current_lang = i18n_data.get("current_language", settings.DEFAULT_LANGUAGE)
|
||||
i18n: Optional[JsonI18n] = i18n_data.get("i18n_instance")
|
||||
if not i18n or not callback.message:
|
||||
@@ -94,96 +111,104 @@ async def cancel_broadcast_at_prompt_stage(callback: types.CallbackQuery,
|
||||
|
||||
try:
|
||||
await callback.message.edit_text(
|
||||
_("admin_broadcast_cancelled_nav_back"), reply_markup=None)
|
||||
_("admin_broadcast_cancelled_nav_back"), reply_markup=None
|
||||
)
|
||||
except Exception:
|
||||
await callback.message.answer(_("admin_broadcast_cancelled_nav_back"))
|
||||
|
||||
await callback.answer(_("admin_broadcast_cancelled_alert"))
|
||||
await state.clear()
|
||||
|
||||
await callback.message.answer(_(key="admin_panel_title"),
|
||||
reply_markup=get_admin_panel_keyboard(
|
||||
i18n, current_lang, settings))
|
||||
await callback.message.answer(
|
||||
_(key="admin_panel_title"),
|
||||
reply_markup=get_admin_panel_keyboard(i18n, current_lang, settings),
|
||||
)
|
||||
|
||||
|
||||
@router.callback_query(F.data.startswith("broadcast_final_action:"),
|
||||
AdminStates.confirming_broadcast)
|
||||
async def confirm_broadcast_callback_handler(callback: types.CallbackQuery,
|
||||
state: FSMContext,
|
||||
i18n_data: dict, bot: Bot,
|
||||
settings: Settings,
|
||||
session: AsyncSession):
|
||||
@router.callback_query(
|
||||
F.data.startswith("broadcast_final_action:"),
|
||||
AdminStates.confirming_broadcast,
|
||||
)
|
||||
async def confirm_broadcast_callback_handler(
|
||||
callback: types.CallbackQuery,
|
||||
state: FSMContext,
|
||||
i18n_data: dict,
|
||||
bot: Bot,
|
||||
settings: Settings,
|
||||
session: AsyncSession,
|
||||
):
|
||||
current_lang = i18n_data.get("current_language", settings.DEFAULT_LANGUAGE)
|
||||
i18n: Optional[JsonI18n] = i18n_data.get("i18n_instance")
|
||||
if not i18n or not callback.message:
|
||||
await callback.answer("Error processing broadcast confirmation.",
|
||||
show_alert=True)
|
||||
await callback.answer("Error processing broadcast confirmation.", show_alert=True)
|
||||
return
|
||||
_ = lambda key, **kwargs: i18n.gettext(current_lang, key, **kwargs)
|
||||
|
||||
action = callback.data.split(":")[1]
|
||||
user_fsm_data = await state.get_data()
|
||||
broadcast_message_to_send = user_fsm_data.get("broadcast_message")
|
||||
|
||||
if action == "send":
|
||||
if not broadcast_message_to_send:
|
||||
await callback.message.edit_text(
|
||||
_("admin_broadcast_error_no_message"))
|
||||
text = user_fsm_data.get("broadcast_text")
|
||||
entities = user_fsm_data.get("broadcast_entities", [])
|
||||
|
||||
if not text:
|
||||
await callback.message.edit_text(_("admin_broadcast_error_no_message"))
|
||||
await state.clear()
|
||||
await callback.answer(_("admin_broadcast_error_no_message_alert"),
|
||||
show_alert=True)
|
||||
await callback.answer(
|
||||
_("admin_broadcast_error_no_message_alert"), show_alert=True
|
||||
)
|
||||
return
|
||||
|
||||
await callback.message.edit_text(_("admin_broadcast_sending_started"),
|
||||
reply_markup=None)
|
||||
await callback.message.edit_text(_("admin_broadcast_sending_started"), reply_markup=None)
|
||||
await callback.answer()
|
||||
|
||||
user_ids_for_broadcast = await user_dal.get_all_active_user_ids_for_broadcast(
|
||||
session)
|
||||
user_ids = await user_dal.get_all_active_user_ids_for_broadcast(session)
|
||||
|
||||
sent_count = 0
|
||||
failed_count = 0
|
||||
logging.info(
|
||||
f"Admin {callback.from_user.id} starting broadcast: '{broadcast_message_to_send[:50]}...' to {len(user_ids_for_broadcast)} users."
|
||||
)
|
||||
admin_user = callback.from_user
|
||||
logging.info(
|
||||
f"Admin {admin_user.id} broadcasting '{text[:50]}...' to {len(user_ids)} users."
|
||||
)
|
||||
|
||||
for user_id_to_send in user_ids_for_broadcast:
|
||||
for uid in user_ids:
|
||||
try:
|
||||
await bot.send_message(user_id_to_send,
|
||||
broadcast_message_to_send,
|
||||
parse_mode="HTML")
|
||||
await bot.send_message(
|
||||
chat_id=uid,
|
||||
text=text,
|
||||
entities=entities,
|
||||
)
|
||||
sent_count += 1
|
||||
|
||||
log_payload_sent = {
|
||||
"user_id": admin_user.id,
|
||||
"telegram_username": admin_user.username,
|
||||
"telegram_first_name": admin_user.first_name,
|
||||
"event_type": "admin_broadcast_sent",
|
||||
"content":
|
||||
f"To user {user_id_to_send}: {broadcast_message_to_send[:70]}...",
|
||||
"is_admin_event": True,
|
||||
"target_user_id": user_id_to_send
|
||||
}
|
||||
await message_log_dal.create_message_log(
|
||||
session, log_payload_sent)
|
||||
session,
|
||||
{
|
||||
"user_id": admin_user.id,
|
||||
"telegram_username": admin_user.username,
|
||||
"telegram_first_name": admin_user.first_name,
|
||||
"event_type": "admin_broadcast_sent",
|
||||
"content": f"To user {uid}: {text[:70]}...",
|
||||
"is_admin_event": True,
|
||||
"target_user_id": uid,
|
||||
},
|
||||
)
|
||||
except Exception as e:
|
||||
failed_count += 1
|
||||
logging.warning(
|
||||
f"Failed to send broadcast to user {user_id_to_send}: {type(e).__name__} - {e}"
|
||||
f"Failed to send broadcast to {uid}: {type(e).__name__} – {e}"
|
||||
)
|
||||
log_payload_failed = {
|
||||
"user_id": admin_user.id,
|
||||
"telegram_username": admin_user.username,
|
||||
"telegram_first_name": admin_user.first_name,
|
||||
"event_type": "admin_broadcast_failed",
|
||||
"content":
|
||||
f"For user {user_id_to_send}: {type(e).__name__} - {str(e)[:70]}...",
|
||||
"is_admin_event": True,
|
||||
"target_user_id": user_id_to_send
|
||||
}
|
||||
await message_log_dal.create_message_log(
|
||||
session, log_payload_failed)
|
||||
session,
|
||||
{
|
||||
"user_id": admin_user.id,
|
||||
"telegram_username": admin_user.username,
|
||||
"telegram_first_name": admin_user.first_name,
|
||||
"event_type": "admin_broadcast_failed",
|
||||
"content": f"For user {uid}: {type(e).__name__} – {str(e)[:70]}...",
|
||||
"is_admin_event": True,
|
||||
"target_user_id": uid,
|
||||
},
|
||||
)
|
||||
await asyncio.sleep(0.05)
|
||||
|
||||
try:
|
||||
@@ -192,17 +217,17 @@ async def confirm_broadcast_callback_handler(callback: types.CallbackQuery,
|
||||
await session.rollback()
|
||||
logging.error(f"Error committing broadcast logs: {e_commit}")
|
||||
|
||||
result_message = _("admin_broadcast_finished_stats",
|
||||
sent_count=sent_count,
|
||||
failed_count=failed_count)
|
||||
result_message = _("admin_broadcast_finished_stats", sent_count=sent_count, failed_count=failed_count)
|
||||
await callback.message.answer(
|
||||
result_message,
|
||||
reply_markup=get_back_to_admin_panel_keyboard(current_lang, i18n))
|
||||
reply_markup=get_back_to_admin_panel_keyboard(current_lang, i18n),
|
||||
)
|
||||
|
||||
elif action == "cancel":
|
||||
await callback.message.edit_text(
|
||||
_("admin_broadcast_cancelled"),
|
||||
reply_markup=get_back_to_admin_panel_keyboard(current_lang, i18n))
|
||||
reply_markup=get_back_to_admin_panel_keyboard(current_lang, i18n),
|
||||
)
|
||||
await callback.answer()
|
||||
|
||||
await state.clear()
|
||||
|
||||
Reference in New Issue
Block a user