Manejador de mensajes y trucos con texto / Sudo Null IT News

¡Saludos! Gracias por sus suscripciones, me gusta y otras respuestas positivas a mis actividades. Continuemos.

Ya hemos discutido:

Esto significa que todo lo que queda de la base de datos es procesar mensajes y trabajar con medios. Después de esto, puedes pasar a temas más complejos y serios, como: pagos en el bot, middleware, estados fsm, paneles de administración, etc. Pero eso es todo más adelante, y hoy veremos los siguientes temas:

  • Objetos de mensaje

  • Posibilidades de envío de mensajes de texto (responder, reenviar, contestar)

  • Posibilidad de trabajar con mensajes de texto (copiar, borrar, reemplazar texto y teclados)

  • Formatear mensajes de texto (HTML)

  • Trucos para mensajes de texto (seguro que no sabías mucho de lo que te voy a mostrar hoy)

A pesar de su aparente sencillez, el tema es bastante importante y serio. Incluso si ya tienes experiencia con aiogram 2 o aiogram 3, te recomiendo encarecidamente que leas este texto.

Llegué a gran parte de lo que se indicará a continuación a través de una serie de errores y obstáculos.

Tipos de mensajes (contenido)

Los bots de Telegram proporcionan los siguientes tipos de mensajes:

  • Pegatinas (gif)

  • Mensajes de vídeo

  • Mensajes de voz

  • Mensaje con foto

  • Mensajes con documentos.

  • Mensajes con grupos de medios (lo más desagradable para un desarrollador, si ya has trabajado con aiogram en el formato de grupo de medios, sabes a qué me refiero).

Cada uno de estos tipos de mensajes se procesa utilizando un controlador de mensajes:

  • Cuando se trabaja con decoradores, se escribe como @dp.message o @router.message (anteriormente la entrada tenía el formato @dp.message_handler).

  • Al registrarse, se escribe como dp.message.register.

Hoy consideraremos solo los mensajes de texto y la próxima vez cerraremos el tema con contenido multimedia. Gran parte de lo que discutiremos hoy tendrá la misma lógica cuando trabajemos con los medios (con algunas excepciones).

Como estamos trabajando con mensajes de texto, nuestro tipo de contenido será TEXTO. Si ya leyó mis artículos anteriores sobre el tema del diagrama 3, entonces sabrá que para detectar dichos mensajes, necesitamos usar el filtro mágico F.text (análogo a content_type=ContentType.TEXT).

Una vez que nuestro filtro mágico ha calculado un mensaje de texto, tiene muchas opciones que puede hacer con el objeto del mensaje (muchas más de las que parece).

Aquí hay una lista completa de posibilidades para trabajar con mensajes en bots de Telegram en aiogram 3.x:

  • Obtener datos de un mensaje (datos de usuario, ID del mensaje, ID del chat, dónde se envió, etc.)

  • Responder a un mensaje usando el objeto bot y usando el objeto de mensaje en sí (lo veremos en detalle más adelante)

  • Copiar o reenviar mensaje

  • Borrar mensaje

  • Simula un bot escribiendo un mensaje de texto

  • Formatear un mensaje u obtener formato de mensaje del usuario

  • Se puede fijar un mensaje (el principio es el mismo que fijar en un chat personal)

  • Cambiar/eliminar teclado del mensaje

Y tenga en cuenta que hemos estado hablando de mensajes de texto todo este tiempo. Ahora pongámonos a practicar.

¿Qué datos del mensaje se utilizan con más frecuencia en la práctica?

Usando mi práctica como ejemplo, diré que la mayoría de las veces trabajo con los siguientes datos del objeto de mensaje (en el contexto de mensajes de texto):

  • Message.message_id (identificación del mensaje)

  • Message.date (fecha y hora en que se envió el mensaje; útil para iniciar sesión)

  • Mensaje.texto (texto del mensaje)

  • Message.html_text (tomamos el texto con etiquetas htm)

  • Message.from_user (aquí puede tomar datos como: nombre de usuario, nombre, apellido, nombre completo, is_premium, etc.)

  • Message.chat (id, tipo, título, nombre de usuario/canal)

Practiquemos para que quede más claro qué es qué.

Adjuntemos el tema con los valores del objeto de mensaje. Imaginemos que tenemos la tarea de escribir un controlador que responda a un mensaje de texto que contenga la palabra “cazador”. Si el bot ve que alguien ha escrito dicho mensaje, realizará 2 acciones:

  1. Responder a un mensaje con algún texto (cita, respuesta regular, respuesta mediante reenvío de mensajes)

  2. El bot generará un diccionario con los siguientes datos:

    • ID de usuario de Telegrama

    • Nombre completo

    • Acceso

    • mensajes de identificación

    • Hora de enviar el mensaje

A continuación, simplemente enviaremos este diccionario a la consola.

Entiendo que el ejemplo puede ser un poco estúpido, pero cuando mires este código, entenderás inmediatamente de qué te estaba hablando aquí.

@start_router.message(F.text.lower().contains('охотник'))
async def cmd_start(message: Message, state: FSMContext):
    # отправка обычного сообщения
    await message.answer('Я думаю, что ты тут про радугу рассказываешь')

    # то же действие, но через объект бота
    await bot.send_message(chat_id=message.from_user.id, text="Для меня это слишком просто")

    # ответ через цитату
    msg = await message.reply('Ну вот что за глупости!?')

    # ответ через цитату, через объект bot
    await bot.send_message(chat_id=message.from_user.id, text="Хотя, это забавно...",
                           reply_to_message_id=msg.message_id)

    await bot.forward_message(chat_id=message.from_user.id, from_chat_id=message.from_user.id, message_id=msg.message_id)

    data_task = {'user_id': message.from_user.id, 'full_name': message.from_user.full_name,
                 'username': message.from_user.username, 'message_id': message.message_id, 'date': get_msc_date(message.date)}
    print(data_task)

Veamos el resultado de la impresión:

{'user_id': 0000000, 'full_name': 'Alexey Yakovenko', 'username': 'yakvenalexx', 'message_id': 337, 'date': datetime.datetime(2024, 6, 13, 19, 53, 1, tzinfo=TzInfo(UTC))}

Se han recibido todos los datos. Tenga en cuenta que la fecha en que se envió el mensaje se especifica en formato de fecha y hora en la zona horaria UTC (hora universal coordinada).

Si necesita convertir a la hora de Moscú, puede utilizar este método:

import pytz

def get_msc_date(utc_time):
    # Задаем московский часовой пояс
    moscow_tz = pytz.timezone('Europe/Moscow')
    # Переводим время в московский часовой пояс
    moscow_time = utc_time.astimezone(moscow_tz)
    return moscow_time

'date': get_msc_date(message.date)

En este caso, este valor se transmitirá en este formato:

datetime.datetime(2024, 6, 13, 22, 57, 10, tzinfo=<DstTzInfo 'Europe/Moscow' MSK+3:00:00 STD>)

Veamos el código

En este código utilizamos varios métodos, pero cada uno de ellos realizaba una tarea: enviar mensajes.

Como vio, algunos de los métodos utilizaron el objeto bot como base y el otro utilizó el mensaje.

# отправка обычного сообщения
await message.answer('Я думаю, что ты тут про радугу рассказываешь')
    
# ответ через цитату
msg = await message.reply('Ну вот что за глупости!?')

Estos dos métodos son bastante convenientes y concisos. Su característica principal es que no requieren una indicación obligatoria del ID a quién se debe enviar el mensaje y a qué mensaje; todo esto ya está oculto en el método de respuesta y respuesta.

Le aconsejo que utilice estos métodos siempre que sea posible.

Métodos como bot.send_message, bot.send_message con el indicador replica_to_message_id y bot.forward_message merecen más atención, al igual que esta entrada:

msg = await message.reply('Ну вот что за глупости!?')

Vayamos en orden.

Al enviar un mensaje usando un bot, un parámetro obligatorio siempre será una indicación de quién necesita enviar el mensaje. En el contexto de mi ejemplo:

await message.answer('Я думаю, что ты тут про радугу рассказываешь')

y

await bot.send_message(chat_id=message.from_user.id, text="Для меня это слишком просто")

Realizamos acciones similares con el mismo resultado y, por lo tanto, no tenía sentido poner esto en un objeto separado, pero hay situaciones en las que enviar mediante mensaje.respuesta es imposible.

Consideremos esta situación usando el ejemplo de enviar mensajes a bots de Telegram desde el panel de administración.

  1. El administrador escribe un mensaje.

  2. Selecciona enviar a todos

  3. El bot toma los ID de los mensajes de la base de datos.

  4. Usando bot.send_message enviará un mensaje.

Conveniente, ¿verdad?

Casos con método bot.forward_message y bot.send_messagecuando indicamos explícitamente qué mensaje debe responderse, es un poco más complicado. La principal dificultad aquí reside en la indicación obligatoria message_id (ID del mensaje a responder/reenviar).

Pero hay una gran ventaja. Cuando conoce el ID del mensaje, tiene posibilidades casi ilimitadas para trabajar con él (cuando el robot es el propietario de este mensaje; de ​​lo contrario, deberá recurrir a trucos, de los que hablaré más adelante). Por ejemplo, puedes volver a grabar constantemente el mismo mensaje, simulando escribir y creando una animación continua (estoy seguro de que has visto esto antes).

Creo que será más fácil explicar esto con un ejemplo específico. Te mostraré cómo hacer que un bot simule escribir.

Importamos:

from aiogram.utils.chat_action import ChatActionSender

El diseño para simular la escritura se verá así:

async with ChatActionSender(bot=bot, chat_id=message.from_user.id, action="typing"):
    await asyncio.sleep(2)

La pausa es necesaria para garantizar que el mensaje de texto no se envíe instantáneamente. Hay una característica interesante aquí. El diseño que describí anteriormente solo es responsable de simular la escritura. Es decir, nada impide alternar la imitación con pausas asincrónicas (await asyncio.sleep), creando una imitación completa de la comunicación con una persona real (ya sabes, cuando te escriben, te distraes y luego continúas escribiendo nuevamente).

Ahora te mostraré cómo puedes cambiar el texto de tu mensaje con el tiempo. Es importante entender aquí que el bot solo puede cambiar aquellos mensajes que él mismo envió. Pero aquí hay un truco interesante que compartiré contigo.

Creemos un controlador que responderá al comando. /test_edit_msg.

@start_router.message(Command('test_edit_msg'))
async def cmd_start(message: Message, state: FSMContext):
    # Бот делает отправку сообщения с сохранением объекта msg
    msg = await message.answer('Отправляю сообщение')

    # Достаем ID сообщения
    msg_id = msg.message_id

    # Имитируем набор текста 2 секунды и отправляеВ коде оставлены комментарии. Единственное, на что нужно обратить внимание, — строка:

м какое-то сообщение
    async with ChatActionSender(bot=bot, chat_id=message.from_user.id, action="typing"):
        await asyncio.sleep(2)
        await message.answer('Новое сообщение')

    # Делаем паузу ещё на 2 секунды
    await asyncio.sleep(2)

    # Изменяем текст сообщения, ID которого мы сохранили
    await bot.edit_message_text(text="<b>Отправляю сообщение!!!</b>", chat_id=message.from_user.id, message_id=msg_id)

Los comentarios se dejan en el código. Lo único a lo que debes prestar atención es a la línea:

await bot.edit_message_text(text="<b>Отправляю сообщение!!!</b>", chat_id=message.from_user.id, message_id=msg_id)

En primer lugar, utilizamos un nuevo método: edit_message_text. Acepta el nuevo texto, el chat en el que se debe cambiar el mensaje y, lo más importante, el ID del mensaje. Después de cambiar el texto de un mensaje, su ID no cambia. Esto significa que mientras exista el mensaje, se podrá reemplazar tantas veces como se desee. Sin embargo, tenga cuidado con este método: si el usuario elimina el mensaje, el bot simplemente fallará al intentar editarlo.

Sin manejo de errores capturamos:

aiogram.exceptions.TelegramBadRequest: Telegram server says - Bad Request: message to edit not found

¡Así que ten cuidado!

Recibiremos un error similar si intentamos cambiar el mensaje de otra persona. Digamos que cambiamos el mensaje con el comando /test_edit_msg:

@start_router.message(Command('test_edit_msg'))
async def cmd_start(message: Message, state: FSMContext):
    # Бот пытается изменить сообщение, которое не отправлял
    await bot.edit_message_text(text="<b>Отправляю сообщение!!!</b>", chat_id=message.from_user.id, message_id=message.message_id)

Obtenemos:

aiogram.exceptions.TelegramBadRequest: Telegram server says - Bad Request: message can't be edited

Aquí el bot dirá que el mensaje no se puede cambiar, y esto es cierto, porque el bot no es el autor de este mensaje. Ahora mira el truco que puedes hacer aquí:

  • El bot se asignará este mensaje a sí mismo (lo copiará).

  • Eliminará un mensaje del usuario.

  • Sobrescribirá un mensaje ya copiado y lo enviará.

Miremos:

@start_router.message(Command('test_edit_msg'))
async def cmd_start(message: Message, state: FSMContext):
    new_msg = await bot.copy_message(
        chat_id=message.from_user.id,
        from_chat_id=message.from_user.id, 
        message_id=message.message_id
    )
    await message.delete()
    await bot.edit_message_text(
        text="<b>Отправляю сообщение!!!</b>",
        chat_id=message.from_user.id,
        message_id=new_msg.message_id
    )

Tenga en cuenta que hemos utilizado nuevos métodos aquí: new_msg = await bot.copy_message y await message.delete(). en el metodo await message.delete() hay un análogo del método bot. Está escrito así:

await bot.delete_message(chat_id=message.from_user.id, message_id=message.message_id)

Como puedes imaginar, realiza la misma acción, pero con una grabación más engorrosa. A veces es muy útil.

Un poco sobre teclados

En artículos anteriores te conté todo sobre el texto y los teclados en línea, y ya sabes que los mensajes, en particular los de texto, pueden ir acompañados de uno u otro tipo de teclado.

Ahora te mostraré cómo quitar el teclado y cómo reemplazar el teclado en el mensaje.

Primero, importemos un método para eliminar teclados (funcionará tanto en teclados de texto como en línea):

from aiogram.types import ReplyKeyboardRemove

Este método debe usarse cuando el escenario requiere que el usuario pase de un estado a otro. Por ejemplo, un teclado de texto con selección de género “Masculino” y “Femenino”. Él hace una elección y luego le espera una nueva pregunta, por ejemplo, “Indique el año de nacimiento”.

Si no quitamos el teclado (no lo reemplazamos por otro como parte del script), el teclado seguirá bloqueándose. Los principiantes tienen este problema cuando el usuario completó el guión y en una de las etapas había que elegir una ciudad. Después de esto, le sigue el teclado con la elección de ciudad.

No dejes que esto suceda. Aquí todo es simple: en lugar de especificar el teclado en el método reply_markup Pásalo ReplyKeyboardRemove(). Definitivamente consideraremos esto en el tema FSM. Ahora me gustaría hablar de otra cosa.

A veces es necesario reemplazar el teclado como parte de un mensaje específico, como por ejemplo con el tiempo. Puedes cambiar el teclado en un mensaje (eliminar) de varias maneras.

Método 1:

await bot.edit_message_text(chat_id=message.chat.id, message_id=msg.message_id, text="Пока!", reply_markup=inline_kb())

Aquí volvemos a utilizar el familiar. edit_message_text. Simplemente reescribimos el mensaje completo y le vinculamos el teclado directamente. Tenga en cuenta: este método espera un teclado en línea. Al pasar un teclado de texto a este método, obtendrás:

input_value=ReplyKeyboardMarkup(keybo...ню:', selective=None), input_type=ReplyKeyboardMarkup) For further information visit https://errors.pydantic.dev/2.7/v/model_type

Pero reemplazar los teclados en línea dentro de uno message_id posible hasta el infinito.

Método 2:

await bot.edit_message_reply_markup(chat_id=message.chat.id, message_id=msg.message_id, reply_markup=inline_kb())

Aquí reemplazamos solo el teclado, dejando el texto sin cambios.

¿Cómo podemos agregar un teclado de texto a un mensaje?

Lamentablemente no existe un método de sustitución directa, pero nada nos impide utilizar la muleta de copiar un mensaje, ¿verdad?

@start_router.message(Command('test_edit_msg'))
async def cmd_start(message: Message, state: FSMContext):
    msg = await message.answer('Привет!')
    await asyncio.sleep(2)
    old_text = msg.text
    await msg.delete()
    await message.answer(old_text, reply_markup=main_kb(message.from_user.id))

Este es nuestro diseño simple. Para no ser tan sofisticado, siempre intento dar preferencia a los teclados en línea y te aconsejo que lo hagas. Bueno, aprenda a entrelazar botones de texto en secuencias de comandos para que aparezcan y desaparezcan de manera lógica (sobrescritos por otros teclados de texto o eliminados usando el ReplyKeyboardRemove()).

Formato de texto

Para ser honesto, nunca uso el formato Markdown cuando trabajo con aiogram 3 y ahora explicaré por qué.

En primer lugar, en lo que a mí respecta, la sintaxis es inconveniente. En segundo lugar, al comienzo de aiogram 3 hubo muchos errores relacionados con el formato Markdown, y aquellos que transfirieron sus proyectos a tres sufrieron mucho porque eligieron Markdown.

A continuación, hablaré sobre el formato usando HTML como ejemplo, pero puedes usar el tipo que te resulte más agradable.

Para no transmitir a cada uno de tus mensajes parse_mode="HTML" Al iniciar un bot, le aconsejo que utilice la siguiente construcción:

from aiogram.client.default import DefaultBotProperties
from aiogram.enums import ParseMode

bot = Bot(token=config('TOKEN'), default=DefaultBotProperties(parse_mode=ParseMode.HTML))

En este caso, el diseño DefaultBotProperties(parse_mode=ParseMode.HTML) establecerá el formato HTML predeterminado para cada mensaje.

Veamos las principales etiquetas HTML que se pueden utilizar para formatear:

<b>Жирный</b>
<i>Курсив</i>
<u>Подчеркнутый</u>
<s>Зачеркнутый</s>
<tg-spoiler>Спойлер (скрытый текст)</tg-spoiler>
<a href=" в тексте</a>
<code>Код с копированием текста при клике</code>
<pre>Спойлер с копированием текста</pre>
Así es como se ve el texto formateado en el bot.

Así es como se ve el texto formateado en el bot.

Úselos con cuidado, ya que hay algo muy desagradable. Algunos usuarios deciden utilizar < y > en sus inicios de sesión o en mensajes enviados al bot.

Si el modo de formato de texto HTML está habilitado en el código, el bot detecta un error:

aiogram.exceptions.TelegramBadRequest: Telegram server says - Bad Request: can't parse entities: Unsupported start tag "<>"

Usamos html.escape para resolver este problema. Importemos:

from html import escape

Y luego simplemente ejecutamos aquellos lugares donde puede ocurrir una etiqueta incorrecta a través del método escape.

Bueno, lo último que te prometí es capturar texto formateado. Ahora te daré un ejemplo para que entiendas inmediatamente lo que quiero decir (práctica real).

En el panel de administración agregué la posibilidad de crear publicaciones con texto formateado (con etiquetas HTML). El cliente formateó el texto antes de enviarlo (simplemente usando métodos estándar de Telegram: resaltado con el mouse, seleccionado “Negrita”, etc.). Después de estos pasos, el texto formateado se envió al bot, que capturó el HTML usando message.html_texty luego realizó una entrada en la base de datos con etiquetas. Luego, al mostrar la publicación, el bot simplemente tomó el texto con etiquetas HTML y envió el texto ya formateado. Se trata de una especie de editor HTML de bolsillo para Telegram.

He aquí un ejemplo sencillo:

@start_router.message(Command('test_edit_msg'))
async def cmd_start(message: Message, state: FSMContext):
    msg = await message.answer('<b>ПРИВЕТ!</b>')
    print(msg.html_text)

El bot mostrado en la consola:

<b>ПРИВЕТ!</b>

Aunque no guardamos una variable con una etiqueta HTML en ninguna parte.

Conclusión

Hoy hemos hecho el máximo análisis del tema del trabajo con mensajes de texto a través del aiograma 3. Ahora puedes hacerlo con la brisa.

Se dedica mucho tiempo, esfuerzo y energía a escribir este tipo de artículos. Por lo tanto, si desea que se publique material similar con más frecuencia, no olvide enviar comentarios a través de suscripciones, me gusta y comentarios. La situación es la siguiente: según algunas publicaciones, mis marcadores superan los 200 y mi karma está al nivel de una falta.

Gracias por su atención. Hasta luego.

Publicaciones Similares

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *