From 2b31bb8c0ab4983367db19d1ed6c57467577eacb Mon Sep 17 00:00:00 2001 From: Stefano Pigozzi Date: Sun, 14 Jul 2024 09:46:20 +0200 Subject: [PATCH] Add rich text formatting features (#6) * Add rich formatting to `/help` * Create `EscapableInTelegramHTML` trait and implement it for types where `String: From` * Add rich formatting to `/reminder` * Add rich formatting to `/whoami` * Add rich formatting to the launch message --- src/services/telegram/commands/help.rs | 8 +++++--- src/services/telegram/commands/reminder.rs | 21 ++++++++++++++------- src/services/telegram/commands/whoami.rs | 7 +++++-- src/services/telegram/escape.rs | 15 +++++++++++++++ src/services/telegram/mod.rs | 18 ++++++++++++++---- 5 files changed, 53 insertions(+), 16 deletions(-) create mode 100644 src/services/telegram/escape.rs diff --git a/src/services/telegram/commands/help.rs b/src/services/telegram/commands/help.rs index 226317ba..03b62195 100644 --- a/src/services/telegram/commands/help.rs +++ b/src/services/telegram/commands/help.rs @@ -2,17 +2,18 @@ use anyhow::Context; use teloxide::Bot; use teloxide::payloads::SendMessageSetters; use teloxide::requests::Requester; -use teloxide::types::{BotCommand, Message}; +use teloxide::types::{BotCommand, Message, ParseMode}; use teloxide::utils::command::BotCommands; use super::{CommandResult}; pub async fn handler_all(bot: &Bot, message: &Message) -> CommandResult { let descriptions = super::Command::descriptions().to_string(); - let text = format!("❓ Sono disponibili i seguenti comandi:\n\n{descriptions}"); + let text = format!("❔ Comandi disponibili\n\n{descriptions}"); let _reply = bot .send_message(message.chat.id, text) + .parse_mode(ParseMode::Html) .reply_to_message_id(message.id) .await .context("Non è stato possibile inviare la risposta.")?; @@ -62,10 +63,11 @@ pub async fn handler_specific(bot: &Bot, message: &Message, target: &str) -> Com true => "", }; - let text = format!("❓ Il comando {}{}:\n\n{}", target.command, display_suffix, target.description); + let text = format!("❔ Comando {}{}\n\n{}", target.command, display_suffix, target.description); let _reply = bot .send_message(message.chat.id, text) + .parse_mode(ParseMode::Html) .reply_to_message_id(message.id) .await .context("Non è stato possibile inviare la risposta.")?; diff --git a/src/services/telegram/commands/reminder.rs b/src/services/telegram/commands/reminder.rs index a748db17..c5a62db9 100644 --- a/src/services/telegram/commands/reminder.rs +++ b/src/services/telegram/commands/reminder.rs @@ -3,10 +3,11 @@ use anyhow::Context; use teloxide::Bot; use teloxide::payloads::SendMessageSetters; use teloxide::requests::Requester; -use teloxide::types::{Message}; +use teloxide::types::{Message, ParseMode}; use parse_datetime::parse_datetime_at_date; use once_cell::sync::Lazy; use regex::Regex; +use crate::services::telegram::escape::EscapableInTelegramHTML; use super::{CommandResult}; @@ -55,14 +56,17 @@ impl FromStr for ReminderArgs { pub async fn handler(bot: &Bot, message: &Message, ReminderArgs { target, reminder}: ReminderArgs) -> CommandResult { let text = format!( - "🕒 Promemoria per {} impostato\n\ + "🕒 Promemoria impostato\n\ + {}\n\ + \n\ {}", - target.format("%c"), - reminder + target.format("%c").to_string().escape_telegram_html(), + reminder.clone().escape_telegram_html() ); let _reply = bot .send_message(message.chat.id, text) + .parse_mode(ParseMode::Html) .reply_to_message_id(message.id) .await .context("Non è stato possibile inviare la conferma.")?; @@ -72,14 +76,17 @@ pub async fn handler(bot: &Bot, message: &Message, ReminderArgs { target, remind tokio::time::sleep(wait_duration).await; let text = format!( - "🕒 Promemoria per {} attivato\n\ + "🕒 Promemoria attivato\n\ + {}\n\ + \n\ {}", - target.format("%c"), - reminder + target.format("%c").to_string().escape_telegram_html(), + reminder.escape_telegram_html() ); let _reply = bot .send_message(message.chat.id, text) + .parse_mode(ParseMode::Html) .reply_to_message_id(message.id) .await .context("Non è stato possibile inviare il promemoria.")?; diff --git a/src/services/telegram/commands/whoami.rs b/src/services/telegram/commands/whoami.rs index 92dc3d7f..a9fbc1a0 100644 --- a/src/services/telegram/commands/whoami.rs +++ b/src/services/telegram/commands/whoami.rs @@ -2,8 +2,9 @@ use anyhow::Context; use teloxide::Bot; use teloxide::payloads::SendMessageSetters; use teloxide::requests::Requester; -use teloxide::types::{Message}; +use teloxide::types::{Message, ParseMode}; use crate::database::models::{RoyalnetUser}; +use crate::services::telegram::escape::EscapableInTelegramHTML; use super::{CommandResult}; pub async fn handler(bot: &Bot, message: &Message) -> CommandResult { @@ -34,11 +35,13 @@ pub async fn handler(bot: &Bot, message: &Message) -> CommandResult { let username = &royalnet_user.username; let text = format!( - "👤 Nel database RYG, tu hai l'username «{username}»." + "👤 Nel database RYG, tu hai l'username {}.", + username.escape_telegram_html(), ); let _reply = bot .send_message(message.chat.id, text) + .parse_mode(ParseMode::Html) .reply_to_message_id(message.id) .await .context("Non è stato possibile inviare la risposta.")?; diff --git a/src/services/telegram/escape.rs b/src/services/telegram/escape.rs new file mode 100644 index 00000000..0f30898d --- /dev/null +++ b/src/services/telegram/escape.rs @@ -0,0 +1,15 @@ +pub trait EscapableInTelegramHTML { + fn escape_telegram_html(self) -> String; +} + +impl EscapableInTelegramHTML for T + where String: From +{ + fn escape_telegram_html(self) -> String { + let s: String = String::from(self); + let s = s.replace("<", "<"); + let s = s.replace(">", ">"); + let s = s.replace("&", "&"); + s + } +} \ No newline at end of file diff --git a/src/services/telegram/mod.rs b/src/services/telegram/mod.rs index 4bd39f40..87aefd83 100644 --- a/src/services/telegram/mod.rs +++ b/src/services/telegram/mod.rs @@ -4,13 +4,16 @@ use anyhow::{Context, Error, Result}; use regex::Regex; use teloxide::dispatching::{DefaultKey, Dispatcher, HandlerExt, UpdateFilterExt}; use teloxide::dptree::entry; +use teloxide::payloads::SendMessageSetters; use teloxide::requests::Requester; -use teloxide::types::{Me, Message, Update}; +use teloxide::types::{Me, Message, ParseMode, Update}; +use crate::services::telegram::escape::EscapableInTelegramHTML; use super::RoyalnetService; #[allow(clippy::needless_pub_self)] pub(self) mod config; mod commands; +pub(self) mod escape; pub struct BotService { pub bot: Bot @@ -32,12 +35,19 @@ impl BotService { let id = &me.user.id; let text = format!( - "💠 Servizio Telegram avviato\n\ - Royalnet v{version}\n\ - @{username} ({id})", + "💠 Servizio Telegram avviato\n\ + \n\ + Royalnet v{}\n\ + \n\ + @{} [{}]", + version.escape_telegram_html(), + version.escape_telegram_html(), + username.escape_telegram_html(), + id.to_string().escape_telegram_html(), ); self.bot.send_message(chat_id, text) + .parse_mode(ParseMode::Html) .await .context("Invio della notifica di avvio non riuscito.")?;