Compare commits

..

8 Commits

Author SHA1 Message Date
Akulij
2590055ea1 fix: enable html parse mode for message sent from support
All checks were successful
Build && Deploy / cargo build (push) Successful in 38s
2025-05-09 00:29:36 +03:00
Akulij
aaed6fcdca store MessageForward in LeaveApplication callback 2025-05-09 00:29:16 +03:00
Akulij
c1c331ab29 return chat and message id in answer message 2025-05-09 00:24:59 +03:00
Akulij
729f0c3eea return teloxide's Message struct in send_application_to_chat 2025-05-09 00:23:41 +03:00
Akulij
27a829b784 create AdminMisconfiguration error 2025-05-09 00:21:54 +03:00
Akulij
c69b6595fd handle replies to MessageForward from support chat 2025-05-08 23:55:23 +03:00
Akulij
091a42bf31 fix: message id in MessageForward should be i32 2025-05-08 23:23:39 +03:00
Akulij
2acda3087b create MessageForward 2025-05-08 23:15:54 +03:00
3 changed files with 144 additions and 15 deletions

62
src/db/message_forward.rs Normal file
View File

@ -0,0 +1,62 @@
use bson::doc;
use serde::{Deserialize, Serialize};
use super::DbResult;
use crate::query_call_consume;
use crate::CallDB;
#[derive(Serialize, Deserialize)]
pub struct MessageForward {
pub _id: bson::oid::ObjectId,
pub chat_id: i64,
pub message_id: i32,
pub source_chat_id: i64,
pub source_message_id: i32,
pub reply: bool,
}
impl MessageForward {
pub fn new(
chat_id: i64,
message_id: i32,
source_chat_id: i64,
source_message_id: i32,
reply: bool,
) -> Self {
Self {
_id: Default::default(),
chat_id,
message_id,
source_chat_id,
source_message_id,
reply,
}
}
query_call_consume!(store, self, db, Self, {
let db = db.get_database().await;
let ci = db.collection::<Self>("message_forward");
ci.insert_one(&self).await?;
Ok(self)
});
pub async fn get<D: CallDB>(
db: &mut D,
chat_id: i64,
message_id: i32,
) -> DbResult<Option<Self>> {
let db = db.get_database().await;
let ci = db.collection::<Self>("message_forward");
let mf = ci
.find_one(doc! {
"chat_id": chat_id,
"message_id": message_id,
})
.await?;
Ok(mf)
}
}

View File

@ -1,5 +1,6 @@
pub mod application; pub mod application;
pub mod callback_info; pub mod callback_info;
pub mod message_forward;
use std::time::Duration; use std::time::Duration;

View File

@ -5,8 +5,10 @@ pub mod utils;
use db::application::Application; use db::application::Application;
use db::callback_info::CallbackInfo; use db::callback_info::CallbackInfo;
use db::message_forward::MessageForward;
use log::{error, info, warn}; use log::{error, info, warn};
use std::time::Duration; use std::time::Duration;
use teloxide::sugar::request::RequestReplyExt;
use utils::create_callback_button; use utils::create_callback_button;
use crate::admin::{admin_command_handler, AdminCommands}; use crate::admin::{admin_command_handler, AdminCommands};
@ -111,6 +113,8 @@ pub enum BotError {
// TODO: not a really good to hardcode types, better to extend it later // TODO: not a really good to hardcode types, better to extend it later
StorageError(#[from] mongodb_storage::MongodbStorageError<<Json as Serializer<State>>::Error>), StorageError(#[from] mongodb_storage::MongodbStorageError<<Json as Serializer<State>>::Error>),
MsgTooOld(String), MsgTooOld(String),
BotLogicError(String),
AdminMisconfiguration(String),
} }
pub type BotResult<T> = Result<T, BotError>; pub type BotResult<T> = Result<T, BotError>;
@ -185,6 +189,12 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
}) })
.endpoint(edit_msg_cmd_handler), .endpoint(edit_msg_cmd_handler),
) )
.branch(
Update::filter_message()
.filter(|msg: Message| msg.reply_to_message().is_some())
.filter(|state: State| matches!(state, State::Start))
.endpoint(support_reply_handler),
)
.branch( .branch(
dptree::case![State::Edit { dptree::case![State::Edit {
literal, literal,
@ -207,6 +217,55 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
Ok(()) Ok(())
} }
async fn support_reply_handler(bot: Bot, mut db: DB, msg: Message) -> BotResult<()> {
use teloxide::utils::render::Renderer;
let rm = match msg.reply_to_message() {
Some(rm) => rm,
None => {
return Err(BotError::BotLogicError(
"support_reply_handler should not be called when no message is replied".to_string(),
));
}
};
let (chat_id, message_id) = (rm.chat.id.0, rm.id.0);
let mf = match MessageForward::get(&mut db, chat_id, message_id).await? {
Some(mf) => mf,
None => {
bot.send_message(msg.chat.id, "No forwarded message found for your reply")
.await?;
return Ok(());
}
};
let text = match msg.kind {
MessageKind::Common(message_common) => match message_common.media_kind {
MediaKind::Text(media_text) => {
Renderer::new(&media_text.text, &media_text.entities).as_html()
}
_ => {
bot.send_message(msg.chat.id, "Only text messages currently supported!")
.await?;
return Ok(());
}
},
// can't hapen because we already have check for reply
_ => unreachable!(),
};
let msg = bot
.send_message(ChatId(mf.source_chat_id), text)
.parse_mode(ParseMode::Html);
let msg = match mf.reply {
false => msg,
true => msg.reply_to(MessageId(mf.source_message_id)),
};
msg.await?;
Ok(())
}
async fn button_edit_callback( async fn button_edit_callback(
bot: Bot, bot: Bot,
mut db: DB, mut db: DB,
@ -382,8 +441,9 @@ async fn callback_handler(bot: Bot, mut db: DB, q: CallbackQuery) -> BotResult<(
} }
Callback::LeaveApplication => { Callback::LeaveApplication => {
let application = Application::new(q.from.clone()).store(&mut db).await?; let application = Application::new(q.from.clone()).store(&mut db).await?;
send_application_to_chat(&bot, &mut db, &application).await?; let msg = send_application_to_chat(&bot, &mut db, &application).await?;
answer_message(
let (chat_id, msg_id) = answer_message(
&bot, &bot,
q.from.id.0 as i64, q.from.id.0 as i64,
&mut db, &mut db,
@ -391,6 +451,9 @@ async fn callback_handler(bot: Bot, mut db: DB, q: CallbackQuery) -> BotResult<(
None as Option<InlineKeyboardMarkup>, None as Option<InlineKeyboardMarkup>,
) )
.await?; .await?;
MessageForward::new(msg.chat.id.0, msg.id.0, chat_id, msg_id, false)
.store(&mut db)
.await?;
} }
Callback::AskQuestion => { Callback::AskQuestion => {
answer_message( answer_message(
@ -411,7 +474,7 @@ async fn send_application_to_chat(
bot: &Bot, bot: &Bot,
db: &mut DB, db: &mut DB,
app: &Application<teloxide::types::User>, app: &Application<teloxide::types::User>,
) -> BotResult<()> { ) -> BotResult<Message> {
let chat_id: i64 = match db.get_literal_value("support_chat_id").await? { let chat_id: i64 = match db.get_literal_value("support_chat_id").await? {
Some(strcid) => match strcid.parse() { Some(strcid) => match strcid.parse() {
Ok(cid) => cid, Ok(cid) => cid,
@ -422,7 +485,7 @@ async fn send_application_to_chat(
app.from app.from
)) ))
.await; .await;
return Ok(()); return Err(BotError::BotLogicError(format!("somewhere in bots logic support_chat_id literal not stored as a number, got: {strcid}")));
} }
}, },
None => { None => {
@ -431,7 +494,9 @@ async fn send_application_to_chat(
app.from app.from
)) ))
.await; .await;
return Ok(()); return Err(BotError::AdminMisconfiguration(format!(
"admin forget to set support_chat_id"
)));
} }
}; };
let msg = match db.get_literal_value("application_format").await? { let msg = match db.get_literal_value("application_format").await? {
@ -447,13 +512,13 @@ async fn send_application_to_chat(
), ),
None => { None => {
notify_admin("format for support_chat_id is not set").await; notify_admin("format for support_chat_id is not set").await;
return Ok(()); return Err(BotError::AdminMisconfiguration(format!(
"admin forget to set application_format"
)));
} }
}; };
bot.send_message(ChatId(chat_id), msg).await?; Ok(bot.send_message(ChatId(chat_id), msg).await?)
Ok(())
} }
/// This is an emergent situation function, so it should not return any Result, but handle Results /// This is an emergent situation function, so it should not return any Result, but handle Results
@ -765,7 +830,7 @@ async fn answer_message<RM: Into<ReplyMarkup>>(
db: &mut DB, db: &mut DB,
literal: &str, literal: &str,
keyboard: Option<RM>, keyboard: Option<RM>,
) -> BotResult<()> { ) -> BotResult<(i64, i32)> {
answer_message_varianted(bot, chat_id, db, literal, None, keyboard).await answer_message_varianted(bot, chat_id, db, literal, None, keyboard).await
} }
@ -776,7 +841,7 @@ async fn answer_message_varianted<RM: Into<ReplyMarkup>>(
literal: &str, literal: &str,
variant: Option<&str>, variant: Option<&str>,
keyboard: Option<RM>, keyboard: Option<RM>,
) -> BotResult<()> { ) -> BotResult<(i64, i32)> {
answer_message_varianted_silence_flag(bot, chat_id, db, literal, variant, false, keyboard).await answer_message_varianted_silence_flag(bot, chat_id, db, literal, variant, false, keyboard).await
} }
@ -788,7 +853,7 @@ async fn answer_message_varianted_silence_flag<RM: Into<ReplyMarkup>>(
variant: Option<&str>, variant: Option<&str>,
silence_non_variant: bool, silence_non_variant: bool,
keyboard: Option<RM>, keyboard: Option<RM>,
) -> BotResult<()> { ) -> BotResult<(i64, i32)> {
let variant_text = match variant { let variant_text = match variant {
Some(variant) => { Some(variant) => {
let value = db.get_literal_alternative_value(literal, variant).await?; let value = db.get_literal_alternative_value(literal, variant).await?;
@ -926,7 +991,7 @@ async fn answer_message_varianted_silence_flag<RM: Into<ReplyMarkup>>(
} }
None => db.set_message_literal(chat_id, msg_id, literal).await?, None => db.set_message_literal(chat_id, msg_id, literal).await?,
}; };
Ok(()) Ok((chat_id, msg_id))
} }
async fn replace_message( async fn replace_message(
@ -971,7 +1036,7 @@ async fn replace_message(
{ {
// fallback to sending message // fallback to sending message
warn!("Fallback into sending message instead of editing because it contains media"); warn!("Fallback into sending message instead of editing because it contains media");
return answer_message_varianted_silence_flag( answer_message_varianted_silence_flag(
bot, bot,
chat_id, chat_id,
db, db,
@ -980,7 +1045,8 @@ async fn replace_message(
true, true,
keyboard, keyboard,
) )
.await; .await?;
return Ok(());
} }
Err(err) => return Err(err.into()), Err(err) => return Err(err.into()),
}; };