migration to JS engine #1
389
src/main.rs
389
src/main.rs
@ -2,6 +2,7 @@ pub mod admin;
|
|||||||
pub mod botscript;
|
pub mod botscript;
|
||||||
pub mod commands;
|
pub mod commands;
|
||||||
pub mod db;
|
pub mod db;
|
||||||
|
pub mod message_answerer;
|
||||||
pub mod mongodb_storage;
|
pub mod mongodb_storage;
|
||||||
pub mod utils;
|
pub mod utils;
|
||||||
|
|
||||||
@ -12,6 +13,7 @@ use db::callback_info::CallbackInfo;
|
|||||||
use db::message_forward::MessageForward;
|
use db::message_forward::MessageForward;
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use log::{error, info, warn};
|
use log::{error, info, warn};
|
||||||
|
use message_answerer::MessageAnswerer;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use std::sync::RwLock;
|
use std::sync::RwLock;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
@ -283,7 +285,8 @@ async fn botscript_command_handler(
|
|||||||
});
|
});
|
||||||
let literal = bm.literal().map_or("", |s| s.as_str());
|
let literal = bm.literal().map_or("", |s| s.as_str());
|
||||||
|
|
||||||
answer_message_varianted(&bot, msg.chat.id.0, &mut db, literal, None, buttons).await?;
|
let ma = MessageAnswerer::new(&bot, &mut db, msg.chat.id.0);
|
||||||
|
ma.answer(literal, None, buttons).await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@ -486,22 +489,18 @@ async fn callback_handler(bot: Bot, mut db: DB, q: CallbackQuery) -> BotResult<(
|
|||||||
create_callback_button("go_home", Callback::GoHome, &mut db).await?
|
create_callback_button("go_home", Callback::GoHome, &mut db).await?
|
||||||
));
|
));
|
||||||
|
|
||||||
replace_message(
|
let chat_id = q.chat_id().map(|i| i.0).unwrap_or(q.from.id.0 as i64);
|
||||||
&bot,
|
let message_id = q.message.map_or_else(
|
||||||
&mut db,
|
|| {
|
||||||
q.chat_id().map(|i| i.0).unwrap_or(q.from.id.0 as i64),
|
Err(BotError::MsgTooOld(
|
||||||
q.message.map_or_else(
|
"Failed to get message id, probably message too old".to_string(),
|
||||||
|| {
|
))
|
||||||
Err(BotError::MsgTooOld(
|
},
|
||||||
"Failed to get message id, probably message too old".to_string(),
|
|m| Ok(m.id().0),
|
||||||
))
|
)?;
|
||||||
},
|
MessageAnswerer::new(&bot, &mut db, chat_id)
|
||||||
|m| Ok(m.id().0),
|
.replace_message(message_id, "more_info_msg", keyboard)
|
||||||
)?,
|
.await?
|
||||||
"more_info_msg",
|
|
||||||
keyboard,
|
|
||||||
)
|
|
||||||
.await?
|
|
||||||
}
|
}
|
||||||
Callback::ProjectPage { id } => {
|
Callback::ProjectPage { id } => {
|
||||||
let nextproject = match db
|
let nextproject = match db
|
||||||
@ -538,68 +537,50 @@ async fn callback_handler(bot: Bot, mut db: DB, q: CallbackQuery) -> BotResult<(
|
|||||||
[create_callback_button("go_home", Callback::GoHome, &mut db).await?]
|
[create_callback_button("go_home", Callback::GoHome, &mut db).await?]
|
||||||
);
|
);
|
||||||
|
|
||||||
replace_message(
|
let chat_id = q.chat_id().map(|i| i.0).unwrap_or(q.from.id.0 as i64);
|
||||||
&bot,
|
let message_id = q.message.map_or_else(
|
||||||
&mut db,
|
|| {
|
||||||
q.chat_id().map(|i| i.0).unwrap_or(q.from.id.0 as i64),
|
Err(BotError::MsgTooOld(
|
||||||
q.message.map_or_else(
|
"Failed to get message id, probably message too old".to_string(),
|
||||||
|| {
|
))
|
||||||
Err(BotError::MsgTooOld(
|
},
|
||||||
"Failed to get message id, probably message too old".to_string(),
|
|m| Ok(m.id().0),
|
||||||
))
|
)?;
|
||||||
},
|
MessageAnswerer::new(&bot, &mut db, chat_id)
|
||||||
|m| Ok(m.id().0),
|
.replace_message(message_id, &format!("project_{}_msg", id), Some(keyboard))
|
||||||
)?,
|
.await?
|
||||||
&format!("project_{}_msg", id),
|
|
||||||
Some(keyboard),
|
|
||||||
)
|
|
||||||
.await?
|
|
||||||
}
|
}
|
||||||
Callback::GoHome => {
|
Callback::GoHome => {
|
||||||
let keyboard = make_start_buttons(&mut db).await?;
|
let keyboard = make_start_buttons(&mut db).await?;
|
||||||
|
|
||||||
replace_message(
|
let chat_id = q.chat_id().map(|i| i.0).unwrap_or(q.from.id.0 as i64);
|
||||||
&bot,
|
let message_id = q.message.map_or_else(
|
||||||
&mut db,
|
|| {
|
||||||
q.chat_id().map(|i| i.0).unwrap_or(q.from.id.0 as i64),
|
Err(BotError::MsgTooOld(
|
||||||
q.message.map_or_else(
|
"Failed to get message id, probably message too old".to_string(),
|
||||||
|| {
|
))
|
||||||
Err(BotError::MsgTooOld(
|
},
|
||||||
"Failed to get message id, probably message too old".to_string(),
|
|m| Ok(m.id().0),
|
||||||
))
|
)?;
|
||||||
},
|
MessageAnswerer::new(&bot, &mut db, chat_id)
|
||||||
|m| Ok(m.id().0),
|
.replace_message(message_id, "start", Some(keyboard))
|
||||||
)?,
|
.await?
|
||||||
"start",
|
|
||||||
Some(keyboard),
|
|
||||||
)
|
|
||||||
.await?
|
|
||||||
}
|
}
|
||||||
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?;
|
||||||
let msg = send_application_to_chat(&bot, &mut db, &application).await?;
|
let msg = send_application_to_chat(&bot, &mut db, &application).await?;
|
||||||
|
|
||||||
let (chat_id, msg_id) = answer_message(
|
let (chat_id, msg_id) = MessageAnswerer::new(&bot, &mut db, q.from.id.0 as i64)
|
||||||
&bot,
|
.answer("left_application_msg", None, None)
|
||||||
q.from.id.0 as i64,
|
.await?;
|
||||||
&mut db,
|
|
||||||
"left_application_msg",
|
|
||||||
None as Option<InlineKeyboardMarkup>,
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
MessageForward::new(msg.chat.id.0, msg.id.0, chat_id, msg_id, false)
|
MessageForward::new(msg.chat.id.0, msg.id.0, chat_id, msg_id, false)
|
||||||
.store(&mut db)
|
.store(&mut db)
|
||||||
.await?;
|
.await?;
|
||||||
}
|
}
|
||||||
Callback::AskQuestion => {
|
Callback::AskQuestion => {
|
||||||
answer_message(
|
MessageAnswerer::new(&bot, &mut db, q.from.id.0 as i64)
|
||||||
&bot,
|
.answer("ask_question_msg", None, None)
|
||||||
q.from.id.0 as i64,
|
.await?;
|
||||||
&mut db,
|
|
||||||
"ask_question_msg",
|
|
||||||
None as Option<InlineKeyboardMarkup>,
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -941,15 +922,9 @@ async fn user_command_handler(
|
|||||||
variant => Some(variant),
|
variant => Some(variant),
|
||||||
};
|
};
|
||||||
let mut db2 = db.clone();
|
let mut db2 = db.clone();
|
||||||
answer_message_varianted(
|
MessageAnswerer::new(&bot, &mut db, msg.chat.id.0)
|
||||||
&bot,
|
.answer("start", variant, Some(make_start_buttons(&mut db2).await?))
|
||||||
msg.chat.id.0,
|
.await?;
|
||||||
&mut db,
|
|
||||||
"start",
|
|
||||||
variant,
|
|
||||||
Some(make_start_buttons(&mut db2).await?),
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
UserCommands::Help => {
|
UserCommands::Help => {
|
||||||
@ -960,272 +935,6 @@ async fn user_command_handler(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn answer_message<RM: Into<ReplyMarkup>>(
|
|
||||||
bot: &Bot,
|
|
||||||
chat_id: i64,
|
|
||||||
db: &mut DB,
|
|
||||||
literal: &str,
|
|
||||||
keyboard: Option<RM>,
|
|
||||||
) -> BotResult<(i64, i32)> {
|
|
||||||
answer_message_varianted(bot, chat_id, db, literal, None, keyboard).await
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn answer_message_varianted<RM: Into<ReplyMarkup>>(
|
|
||||||
bot: &Bot,
|
|
||||||
chat_id: i64,
|
|
||||||
db: &mut DB,
|
|
||||||
literal: &str,
|
|
||||||
variant: Option<&str>,
|
|
||||||
keyboard: Option<RM>,
|
|
||||||
) -> BotResult<(i64, i32)> {
|
|
||||||
answer_message_varianted_silence_flag(bot, chat_id, db, literal, variant, false, keyboard).await
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn answer_message_varianted_silence_flag<RM: Into<ReplyMarkup>>(
|
|
||||||
bot: &Bot,
|
|
||||||
chat_id: i64,
|
|
||||||
db: &mut DB,
|
|
||||||
literal: &str,
|
|
||||||
variant: Option<&str>,
|
|
||||||
silence_non_variant: bool,
|
|
||||||
keyboard: Option<RM>,
|
|
||||||
) -> BotResult<(i64, i32)> {
|
|
||||||
let variant_text = match variant {
|
|
||||||
Some(variant) => {
|
|
||||||
let value = db.get_literal_alternative_value(literal, variant).await?;
|
|
||||||
if value.is_none() && !silence_non_variant {
|
|
||||||
notify_admin(&format!("variant {variant} for literal {literal} is not found! falling back to just literal")).await;
|
|
||||||
}
|
|
||||||
value
|
|
||||||
}
|
|
||||||
None => None,
|
|
||||||
};
|
|
||||||
let text = match variant_text {
|
|
||||||
Some(text) => text,
|
|
||||||
None => db
|
|
||||||
.get_literal_value(literal)
|
|
||||||
.await?
|
|
||||||
.unwrap_or("Please, set content of this message".into()),
|
|
||||||
};
|
|
||||||
|
|
||||||
let media = db.get_media(literal).await?;
|
|
||||||
let (chat_id, msg_id) = match media.len() {
|
|
||||||
// just a text
|
|
||||||
0 => {
|
|
||||||
let msg = bot.send_message(ChatId(chat_id), text);
|
|
||||||
let msg = match keyboard {
|
|
||||||
Some(kbd) => msg.reply_markup(kbd),
|
|
||||||
None => msg,
|
|
||||||
};
|
|
||||||
let msg = msg.parse_mode(teloxide::types::ParseMode::Html);
|
|
||||||
info!("ENTS: {:?}", msg.entities);
|
|
||||||
let msg = msg.await?;
|
|
||||||
|
|
||||||
(msg.chat.id.0, msg.id.0)
|
|
||||||
}
|
|
||||||
// single media
|
|
||||||
1 => {
|
|
||||||
let media = &media[0]; // safe, cause we just checked len
|
|
||||||
match media.media_type.as_str() {
|
|
||||||
"photo" => {
|
|
||||||
let msg = bot.send_photo(
|
|
||||||
ChatId(chat_id),
|
|
||||||
InputFile::file_id(media.file_id.to_string()),
|
|
||||||
);
|
|
||||||
let msg = match text.as_str() {
|
|
||||||
"" => msg,
|
|
||||||
text => msg.caption(text),
|
|
||||||
};
|
|
||||||
let msg = match keyboard {
|
|
||||||
Some(kbd) => msg.reply_markup(kbd),
|
|
||||||
None => msg,
|
|
||||||
};
|
|
||||||
|
|
||||||
let msg = msg.parse_mode(teloxide::types::ParseMode::Html);
|
|
||||||
let msg = msg.await?;
|
|
||||||
|
|
||||||
(msg.chat.id.0, msg.id.0)
|
|
||||||
}
|
|
||||||
"video" => {
|
|
||||||
let msg = bot.send_video(
|
|
||||||
ChatId(chat_id),
|
|
||||||
InputFile::file_id(media.file_id.to_string()),
|
|
||||||
);
|
|
||||||
let msg = match text.as_str() {
|
|
||||||
"" => msg,
|
|
||||||
text => msg.caption(text),
|
|
||||||
};
|
|
||||||
let msg = match keyboard {
|
|
||||||
Some(kbd) => msg.reply_markup(kbd),
|
|
||||||
None => msg,
|
|
||||||
};
|
|
||||||
|
|
||||||
let msg = msg.parse_mode(teloxide::types::ParseMode::Html);
|
|
||||||
let msg = msg.await?;
|
|
||||||
|
|
||||||
(msg.chat.id.0, msg.id.0)
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// >= 2, should use media group
|
|
||||||
_ => {
|
|
||||||
let media: Vec<InputMedia> = media
|
|
||||||
.into_iter()
|
|
||||||
.enumerate()
|
|
||||||
.map(|(i, m)| {
|
|
||||||
let ifile = InputFile::file_id(m.file_id);
|
|
||||||
let caption = if i == 0 {
|
|
||||||
match text.as_str() {
|
|
||||||
"" => None,
|
|
||||||
text => Some(text.to_string()),
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
match m.media_type.as_str() {
|
|
||||||
"photo" => InputMedia::Photo(teloxide::types::InputMediaPhoto {
|
|
||||||
media: ifile,
|
|
||||||
caption,
|
|
||||||
parse_mode: Some(ParseMode::Html),
|
|
||||||
caption_entities: None,
|
|
||||||
has_spoiler: false,
|
|
||||||
show_caption_above_media: false,
|
|
||||||
}),
|
|
||||||
"video" => InputMedia::Video(teloxide::types::InputMediaVideo {
|
|
||||||
media: ifile,
|
|
||||||
thumbnail: None,
|
|
||||||
caption,
|
|
||||||
parse_mode: Some(ParseMode::Html),
|
|
||||||
caption_entities: None,
|
|
||||||
show_caption_above_media: false,
|
|
||||||
width: None,
|
|
||||||
height: None,
|
|
||||||
duration: None,
|
|
||||||
supports_streaming: None,
|
|
||||||
has_spoiler: false,
|
|
||||||
}),
|
|
||||||
_ => {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
let msg = bot.send_media_group(ChatId(chat_id), media);
|
|
||||||
|
|
||||||
let msg = msg.await?;
|
|
||||||
|
|
||||||
(msg[0].chat.id.0, msg[0].id.0)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
match variant {
|
|
||||||
Some(variant) => {
|
|
||||||
db.set_message_literal_variant(chat_id, msg_id, literal, variant)
|
|
||||||
.await?
|
|
||||||
}
|
|
||||||
None => db.set_message_literal(chat_id, msg_id, literal).await?,
|
|
||||||
};
|
|
||||||
Ok((chat_id, msg_id))
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn replace_message(
|
|
||||||
bot: &Bot,
|
|
||||||
db: &mut DB,
|
|
||||||
chat_id: i64,
|
|
||||||
message_id: i32,
|
|
||||||
literal: &str,
|
|
||||||
keyboard: Option<InlineKeyboardMarkup>,
|
|
||||||
) -> BotResult<()> {
|
|
||||||
let variant = db
|
|
||||||
.get_message(chat_id, message_id)
|
|
||||||
.await?
|
|
||||||
.and_then(|m| m.variant);
|
|
||||||
let variant_text = match variant {
|
|
||||||
Some(ref variant) => db.get_literal_alternative_value(literal, variant).await?,
|
|
||||||
None => None,
|
|
||||||
};
|
|
||||||
let text = match variant_text {
|
|
||||||
Some(ref text) => text.to_string(),
|
|
||||||
None => db
|
|
||||||
.get_literal_value(literal)
|
|
||||||
.await?
|
|
||||||
.unwrap_or("Please, set content of this message".into()),
|
|
||||||
};
|
|
||||||
let media = db.get_media(literal).await?;
|
|
||||||
let (chat_id, msg_id) = match media.len() {
|
|
||||||
// just a text
|
|
||||||
0 => {
|
|
||||||
let msg = bot.edit_message_text(ChatId(chat_id), MessageId(message_id), text);
|
|
||||||
let msg = match keyboard {
|
|
||||||
Some(ref kbd) => msg.reply_markup(kbd.clone()),
|
|
||||||
None => msg,
|
|
||||||
};
|
|
||||||
let msg = msg.parse_mode(teloxide::types::ParseMode::Html);
|
|
||||||
info!("ENTS: {:?}", msg.entities);
|
|
||||||
let msg = match msg.await {
|
|
||||||
Ok(msg) => msg,
|
|
||||||
Err(teloxide::RequestError::Api(teloxide::ApiError::Unknown(errtext)))
|
|
||||||
if errtext.as_str()
|
|
||||||
== "Bad Request: there is no text in the message to edit" =>
|
|
||||||
{
|
|
||||||
// fallback to sending message
|
|
||||||
warn!("Fallback into sending message instead of editing because it contains media");
|
|
||||||
answer_message_varianted_silence_flag(
|
|
||||||
bot,
|
|
||||||
chat_id,
|
|
||||||
db,
|
|
||||||
literal,
|
|
||||||
variant.as_deref(),
|
|
||||||
true,
|
|
||||||
keyboard,
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
Err(err) => return Err(err.into()),
|
|
||||||
};
|
|
||||||
|
|
||||||
(msg.chat.id.0, msg.id.0)
|
|
||||||
}
|
|
||||||
// single media
|
|
||||||
1 => {
|
|
||||||
let media = &media[0]; // safe, cause we just checked len
|
|
||||||
let input_file = InputFile::file_id(media.file_id.to_string());
|
|
||||||
let media = match media.media_type.as_str() {
|
|
||||||
"photo" => InputMedia::Photo(teloxide::types::InputMediaPhoto::new(input_file)),
|
|
||||||
"video" => InputMedia::Video(teloxide::types::InputMediaVideo::new(input_file)),
|
|
||||||
_ => todo!(),
|
|
||||||
};
|
|
||||||
bot.edit_message_media(ChatId(chat_id), MessageId(message_id), media)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
let msg = bot.edit_message_caption(ChatId(chat_id), MessageId(message_id));
|
|
||||||
let msg = match text.as_str() {
|
|
||||||
"" => msg,
|
|
||||||
text => msg.caption(text),
|
|
||||||
};
|
|
||||||
let msg = match keyboard {
|
|
||||||
Some(kbd) => msg.reply_markup(kbd),
|
|
||||||
None => msg,
|
|
||||||
};
|
|
||||||
|
|
||||||
let msg = msg.parse_mode(teloxide::types::ParseMode::Html);
|
|
||||||
let msg = msg.await?;
|
|
||||||
|
|
||||||
(msg.chat.id.0, msg.id.0)
|
|
||||||
}
|
|
||||||
// >= 2, should use media group
|
|
||||||
_ => {
|
|
||||||
unreachable!();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
db.set_message_literal(chat_id, msg_id, literal).await?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn make_start_buttons(db: &mut DB) -> BotResult<InlineKeyboardMarkup> {
|
async fn make_start_buttons(db: &mut DB) -> BotResult<InlineKeyboardMarkup> {
|
||||||
let mut buttons: Vec<Vec<InlineKeyboardButton>> = Vec::new();
|
let mut buttons: Vec<Vec<InlineKeyboardButton>> = Vec::new();
|
||||||
buttons.push(vec![
|
buttons.push(vec![
|
||||||
|
|||||||
302
src/message_answerer.rs
Normal file
302
src/message_answerer.rs
Normal file
@ -0,0 +1,302 @@
|
|||||||
|
use log::{info, warn};
|
||||||
|
use teloxide::prelude::*;
|
||||||
|
use teloxide::types::{
|
||||||
|
InputFile, InputMedia, InputMediaPhoto, InputMediaVideo, MessageId, ParseMode,
|
||||||
|
};
|
||||||
|
use teloxide::{
|
||||||
|
types::{ChatId, InlineKeyboardMarkup},
|
||||||
|
Bot,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::db::Media;
|
||||||
|
use crate::{
|
||||||
|
db::{CallDB, DB},
|
||||||
|
notify_admin, BotResult,
|
||||||
|
};
|
||||||
|
|
||||||
|
macro_rules! send_media {
|
||||||
|
($self:ident, $method:ident, $chat_id:expr, $file_id: expr, $text: expr, $keyboard: expr) => {{
|
||||||
|
let msg = $self
|
||||||
|
.bot
|
||||||
|
.$method(ChatId($chat_id), InputFile::file_id($file_id.to_string()));
|
||||||
|
let msg = match $text.as_str() {
|
||||||
|
"" => msg,
|
||||||
|
text => msg.caption(text),
|
||||||
|
};
|
||||||
|
let msg = match $keyboard {
|
||||||
|
Some(kbd) => msg.reply_markup(kbd),
|
||||||
|
None => msg,
|
||||||
|
};
|
||||||
|
let msg = msg.parse_mode(teloxide::types::ParseMode::Html);
|
||||||
|
|
||||||
|
let msg = msg.await?;
|
||||||
|
Ok((msg.chat.id.0, msg.id.0))
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct MessageAnswerer<'a> {
|
||||||
|
bot: &'a Bot,
|
||||||
|
chat_id: i64,
|
||||||
|
db: &'a mut DB,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> MessageAnswerer<'a> {
|
||||||
|
pub fn new(bot: &'a Bot, db: &'a mut DB, chat_id: i64) -> Self {
|
||||||
|
Self { bot, chat_id, db }
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_text(
|
||||||
|
&mut self,
|
||||||
|
literal: &str,
|
||||||
|
variant: Option<&str>,
|
||||||
|
is_replace: bool,
|
||||||
|
) -> BotResult<String> {
|
||||||
|
let variant_text = match variant {
|
||||||
|
Some(variant) => {
|
||||||
|
let value = self
|
||||||
|
.db
|
||||||
|
.get_literal_alternative_value(literal, variant)
|
||||||
|
.await?;
|
||||||
|
if value.is_none() && !is_replace {
|
||||||
|
notify_admin(&format!("variant {variant} for literal {literal} is not found! falling back to just literal")).await;
|
||||||
|
}
|
||||||
|
value
|
||||||
|
}
|
||||||
|
None => None,
|
||||||
|
};
|
||||||
|
let text = match variant_text {
|
||||||
|
Some(text) => text,
|
||||||
|
None => self
|
||||||
|
.db
|
||||||
|
.get_literal_value(literal)
|
||||||
|
.await?
|
||||||
|
.unwrap_or("Please, set content of this message".into()),
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(text)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn answer(
|
||||||
|
mut self,
|
||||||
|
literal: &str,
|
||||||
|
variant: Option<&str>,
|
||||||
|
keyboard: Option<InlineKeyboardMarkup>,
|
||||||
|
) -> BotResult<(i64, i32)> {
|
||||||
|
let text = self.get_text(literal, variant, false).await?;
|
||||||
|
self.answer_inner(text, literal, variant, keyboard).await
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn answer_inner(
|
||||||
|
mut self,
|
||||||
|
text: String,
|
||||||
|
literal: &str,
|
||||||
|
variant: Option<&str>,
|
||||||
|
keyboard: Option<InlineKeyboardMarkup>,
|
||||||
|
) -> BotResult<(i64, i32)> {
|
||||||
|
let media = self.db.get_media(literal).await?;
|
||||||
|
let (chat_id, msg_id) = match media.len() {
|
||||||
|
// just a text
|
||||||
|
0 => self.send_message(text, keyboard).await?,
|
||||||
|
// single media
|
||||||
|
1 => self.send_media(&media[0], text, keyboard).await?,
|
||||||
|
// >= 2, should use media group
|
||||||
|
_ => self.send_media_group(media, text).await?,
|
||||||
|
};
|
||||||
|
self.store_message_info(msg_id, literal, variant).await?;
|
||||||
|
Ok((chat_id, msg_id))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn replace_message(
|
||||||
|
mut self,
|
||||||
|
message_id: i32,
|
||||||
|
literal: &str,
|
||||||
|
keyboard: Option<InlineKeyboardMarkup>,
|
||||||
|
) -> BotResult<()> {
|
||||||
|
let variant = self
|
||||||
|
.db
|
||||||
|
.get_message(self.chat_id, message_id)
|
||||||
|
.await?
|
||||||
|
.and_then(|m| m.variant);
|
||||||
|
let text = self.get_text(literal, variant.as_deref(), true).await?;
|
||||||
|
let media = self.db.get_media(literal).await?;
|
||||||
|
let (chat_id, msg_id) = match media.len() {
|
||||||
|
// just a text
|
||||||
|
0 => {
|
||||||
|
let msg =
|
||||||
|
self.bot
|
||||||
|
.edit_message_text(ChatId(self.chat_id), MessageId(message_id), &text);
|
||||||
|
let msg = match keyboard {
|
||||||
|
Some(ref kbd) => msg.reply_markup(kbd.clone()),
|
||||||
|
None => msg,
|
||||||
|
};
|
||||||
|
let msg = msg.parse_mode(teloxide::types::ParseMode::Html);
|
||||||
|
info!("ENTS: {:?}", msg.entities);
|
||||||
|
let msg = match msg.await {
|
||||||
|
Ok(msg) => msg,
|
||||||
|
Err(teloxide::RequestError::Api(teloxide::ApiError::Unknown(errtext)))
|
||||||
|
if errtext.as_str()
|
||||||
|
== "Bad Request: there is no text in the message to edit" =>
|
||||||
|
{
|
||||||
|
// fallback to sending message
|
||||||
|
warn!("Fallback into sending message instead of editing because it contains media");
|
||||||
|
self.answer_inner(text, literal, variant.as_deref(), keyboard)
|
||||||
|
.await?;
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
Err(err) => return Err(err.into()),
|
||||||
|
};
|
||||||
|
|
||||||
|
(msg.chat.id.0, msg.id.0)
|
||||||
|
}
|
||||||
|
// single media
|
||||||
|
1 => {
|
||||||
|
let media = &media[0]; // safe, cause we just checked len
|
||||||
|
let input_file = InputFile::file_id(media.file_id.to_string());
|
||||||
|
let media = match media.media_type.as_str() {
|
||||||
|
"photo" => InputMedia::Photo(teloxide::types::InputMediaPhoto::new(input_file)),
|
||||||
|
"video" => InputMedia::Video(teloxide::types::InputMediaVideo::new(input_file)),
|
||||||
|
_ => todo!(),
|
||||||
|
};
|
||||||
|
self.bot
|
||||||
|
.edit_message_media(ChatId(self.chat_id), MessageId(message_id), media)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let msg = self
|
||||||
|
.bot
|
||||||
|
.edit_message_caption(ChatId(self.chat_id), MessageId(message_id));
|
||||||
|
let msg = match text.as_str() {
|
||||||
|
"" => msg,
|
||||||
|
text => msg.caption(text),
|
||||||
|
};
|
||||||
|
let msg = match keyboard {
|
||||||
|
Some(kbd) => msg.reply_markup(kbd),
|
||||||
|
None => msg,
|
||||||
|
};
|
||||||
|
|
||||||
|
let msg = msg.parse_mode(teloxide::types::ParseMode::Html);
|
||||||
|
let msg = msg.await?;
|
||||||
|
|
||||||
|
(msg.chat.id.0, msg.id.0)
|
||||||
|
}
|
||||||
|
// >= 2, should use media group
|
||||||
|
_ => {
|
||||||
|
todo!();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
self.store_message_info(msg_id, literal, variant.as_deref())
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn store_message_info(
|
||||||
|
&mut self,
|
||||||
|
message_id: i32,
|
||||||
|
literal: &str,
|
||||||
|
variant: Option<&str>,
|
||||||
|
) -> BotResult<()> {
|
||||||
|
match variant {
|
||||||
|
Some(variant) => {
|
||||||
|
self.db
|
||||||
|
.set_message_literal_variant(self.chat_id, message_id, literal, variant)
|
||||||
|
.await?
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
self.db
|
||||||
|
.set_message_literal(self.chat_id, message_id, literal)
|
||||||
|
.await?
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn send_message(
|
||||||
|
&self,
|
||||||
|
text: String,
|
||||||
|
keyboard: Option<InlineKeyboardMarkup>,
|
||||||
|
) -> BotResult<(i64, i32)> {
|
||||||
|
let msg = self.bot.send_message(ChatId(self.chat_id), text);
|
||||||
|
let msg = match keyboard {
|
||||||
|
Some(kbd) => msg.reply_markup(kbd),
|
||||||
|
None => msg,
|
||||||
|
};
|
||||||
|
let msg = msg.parse_mode(teloxide::types::ParseMode::Html);
|
||||||
|
info!("ENTS: {:?}", msg.entities);
|
||||||
|
let msg = msg.await?;
|
||||||
|
|
||||||
|
Ok((msg.chat.id.0, msg.id.0))
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn send_media(
|
||||||
|
&self,
|
||||||
|
media: &Media,
|
||||||
|
text: String,
|
||||||
|
keyboard: Option<InlineKeyboardMarkup>,
|
||||||
|
) -> BotResult<(i64, i32)> {
|
||||||
|
match media.media_type.as_str() {
|
||||||
|
"photo" => {
|
||||||
|
send_media!(
|
||||||
|
self,
|
||||||
|
send_photo,
|
||||||
|
self.chat_id,
|
||||||
|
media.file_id,
|
||||||
|
text,
|
||||||
|
keyboard
|
||||||
|
)
|
||||||
|
}
|
||||||
|
"video" => {
|
||||||
|
send_media!(
|
||||||
|
self,
|
||||||
|
send_video,
|
||||||
|
self.chat_id,
|
||||||
|
media.file_id,
|
||||||
|
text,
|
||||||
|
keyboard
|
||||||
|
)
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn send_media_group(&self, media: Vec<Media>, text: String) -> BotResult<(i64, i32)> {
|
||||||
|
let media: Vec<InputMedia> = media
|
||||||
|
.into_iter()
|
||||||
|
.enumerate()
|
||||||
|
.map(|(i, m)| {
|
||||||
|
let ifile = InputFile::file_id(m.file_id);
|
||||||
|
let caption = if i == 0 {
|
||||||
|
match text.as_str() {
|
||||||
|
"" => None,
|
||||||
|
text => Some(text.to_string()),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
match m.media_type.as_str() {
|
||||||
|
"photo" => InputMedia::Photo(InputMediaPhoto {
|
||||||
|
caption,
|
||||||
|
parse_mode: Some(ParseMode::Html),
|
||||||
|
..InputMediaPhoto::new(ifile)
|
||||||
|
}),
|
||||||
|
"video" => InputMedia::Video(InputMediaVideo {
|
||||||
|
caption,
|
||||||
|
parse_mode: Some(ParseMode::Html),
|
||||||
|
..InputMediaVideo::new(ifile)
|
||||||
|
}),
|
||||||
|
_ => {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
let msg = self.bot.send_media_group(ChatId(self.chat_id), media);
|
||||||
|
|
||||||
|
let msg = msg.await?;
|
||||||
|
|
||||||
|
Ok((msg[0].chat.id.0, msg[0].id.0))
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user