use anyhow::Result;
use async_trait::async_trait;
use rand::distributions::Alphanumeric;
use rand::Rng;
use std::{collections::HashMap, sync::Arc, time::Duration};
use tokio::sync::{mpsc, Mutex};
use crate::{
api::{self, ApiResponse},
client::Post,
Update,
};
pub struct FakeChat {
pub chat_id: i64,
pub from: String,
chat_tx: Arc<tokio::sync::mpsc::Sender<Update>>,
chat_rx: Arc<Mutex<tokio::sync::mpsc::Receiver<Update>>>,
}
impl FakeChat {
pub async fn send_text(&self, text: impl Into<String>) -> anyhow::Result<()> {
let text = text.into();
let chat_id = self.chat_id;
let from = self.from.clone();
let chat_tx = Arc::clone(&self.chat_tx);
Ok(chat_tx
.send(Update::Message(
FakeMessage::text(chat_id, from, text).into(),
))
.await?)
}
pub async fn edit_text(&self, message_id: i64, text: impl Into<String>) -> anyhow::Result<()> {
let text = text.into();
let chat_id = self.chat_id;
let from = self.from.clone();
let chat_tx = Arc::clone(&self.chat_tx);
let mut message: api::Message = FakeMessage::text(chat_id, from, text).into();
message.message_id = message_id;
Ok(chat_tx.send(Update::EditedMessage(message)).await?)
}
pub async fn send_callback_query(&self, data: impl Into<String>) -> anyhow::Result<()> {
let data = data.into();
let chat_id = self.chat_id;
let from = self.from.clone();
let chat_tx = Arc::clone(&self.chat_tx);
Ok(chat_tx
.send(Update::CallbackQuery(api::CallbackQuery {
id: rand::thread_rng()
.sample_iter(&Alphanumeric)
.take(7)
.map(char::from)
.collect(),
from: from.clone().into(),
message: Some(FakeMessage::text(chat_id, from, "callback query").into()),
inline_message_id: None,
data: Some(data),
}))
.await?)
}
pub async fn send_update(&self, update: Update) -> anyhow::Result<()> {
let chat_tx = Arc::clone(&self.chat_tx);
Ok(chat_tx.send(update).await?)
}
pub async fn recv_update(&self) -> Option<Update> {
let mut rx = self.chat_rx.lock().await;
rx.recv().await
}
}
#[derive(Clone)]
pub struct FakeAPI {
pub bot_name: String,
pub update_id: Arc<Mutex<i64>>,
pub chat_tx: Arc<mpsc::Sender<Update>>,
pub chat_rx: Arc<Mutex<mpsc::Receiver<Update>>>,
pub chat_map: Arc<Mutex<HashMap<i64, Arc<mpsc::Sender<Update>>>>>,
}
impl Default for FakeAPI {
fn default() -> Self {
Self::new()
}
}
impl FakeAPI {
pub fn new() -> Self {
let (tx, rx) = tokio::sync::mpsc::channel(100);
Self {
bot_name: "mobot".to_string(),
update_id: Arc::new(Mutex::new(0)),
chat_tx: Arc::new(tx),
chat_rx: Arc::new(Mutex::new(rx)),
chat_map: Arc::new(Mutex::new(HashMap::new())),
}
}
pub async fn create_chat(&self, from: impl Into<String>) -> FakeChat {
let chat_id = rand::random();
let (tx, rx) = mpsc::channel(100);
self.chat_map.lock().await.insert(chat_id, Arc::new(tx));
FakeChat {
chat_id,
from: from.into(),
chat_tx: Arc::clone(&self.chat_tx),
chat_rx: Arc::new(Mutex::new(rx)),
}
}
async fn get_updates(&self, req: api::GetUpdatesRequest) -> ApiResponse<Vec<api::Update>> {
let update_id = {
let mut update_id = self.update_id.lock().await;
*update_id += 1;
*update_id
};
let mut rx = self.chat_rx.lock().await;
tokio::select! {
Some(msg) = rx.recv() => {
match &msg {
Update::Message(msg) => {
ApiResponse::Ok(vec![api::Update {
update_id,
message: Some(msg.clone()),
..Default::default()
}])
}
Update::EditedMessage(msg) => {
ApiResponse::Ok(vec![api::Update {
update_id,
edited_message: Some(msg.clone()),
..Default::default()
}])
}
Update::CallbackQuery(query) => {
ApiResponse::Ok(vec![api::Update {
update_id,
callback_query: Some(query.clone()),
..Default::default()
}])
}
_ => { unimplemented!() }
}
}
_ = tokio::time::sleep(Duration::from_secs(req.timeout.unwrap_or(1000) as u64)) => {
ApiResponse::Ok(vec![])
}
}
}
async fn send_message(&self, req: api::SendMessageRequest) -> ApiResponse<api::Message> {
let mut message = api::Message::fake(self.bot_name.as_str());
message.chat.id = req.chat_id;
message.text = Some(req.text);
message.reply_to_message = req.reply_to_message_id;
if let Some(chat) = self.chat_map.lock().await.get(&req.chat_id) {
chat.send(Update::Message(message.clone())).await.unwrap();
} else {
warn!("Can't find Chat with id = {}", req.chat_id);
}
ApiResponse::Ok(message)
}
async fn edit_message_text(
&self,
req: api::EditMessageTextRequest,
) -> ApiResponse<api::Message> {
let mut message = api::Message::fake(self.bot_name.as_str());
message.chat.id = req.base.chat_id.unwrap();
message.message_id = req.base.message_id.unwrap();
message.text = Some(req.text);
if let Some(chat) = self.chat_map.lock().await.get(&message.chat.id) {
chat.send(Update::EditedMessage(message.clone()))
.await
.unwrap();
} else {
warn!("Can't find Chat with id = {}", &message.chat.id);
}
ApiResponse::Ok(message)
}
async fn edit_message_reply_markup(
&self,
req: api::EditMessageReplyMarkupRequest,
) -> ApiResponse<api::Message> {
let mut message = api::Message::fake(self.bot_name.as_str());
message.chat.id = req.base.chat_id.unwrap();
message.message_id = req.base.message_id.unwrap();
message.reply_markup = Some(req.base.reply_markup.unwrap().into());
if let Some(chat) = self.chat_map.lock().await.get(&message.chat.id) {
chat.send(Update::EditedMessage(message.clone()))
.await
.unwrap();
} else {
warn!("Can't find Chat with id = {}", &message.chat.id);
}
ApiResponse::Ok(message)
}
}
#[async_trait]
impl Post for FakeAPI {
async fn post(&self, method: String, req: String) -> Result<String> {
use serde_json::from_str as to_json;
use serde_json::to_string as from_json;
debug!("method = {}, req = {}", method, req);
let response = match method.as_str() {
"getUpdates" => from_json(&self.get_updates(to_json(req.as_str())?).await),
"sendMessage" => from_json(&self.send_message(to_json(req.as_str())?).await),
"editMessageText" => from_json(
&self
.edit_message_text(serde_json::from_str(req.as_str())?)
.await,
),
"editMessageReplyMarkup" => {
from_json(&self.edit_message_reply_markup(to_json(req.as_str())?).await)
}
_ => {
warn!("Unknown method: {}", method);
from_json(&ApiResponse::<()>::Err(format!(
"Unknown method: {}",
method
)))
}
};
let body = response.unwrap();
Ok(body)
}
}
#[derive(Debug, Clone)]
pub struct FakeMessage {
chat_id: i64,
from: String,
text: String,
}
impl FakeMessage {
pub fn text(chat_id: i64, from: impl Into<String>, text: impl Into<String>) -> Self {
Self {
chat_id,
from: from.into(),
text: text.into(),
}
}
}
impl From<FakeMessage> for api::Message {
fn from(m: FakeMessage) -> Self {
api::Message {
from: Some(api::User {
id: 1,
first_name: m.from.clone(),
username: Some(m.from.clone()),
..Default::default()
}),
chat: api::Chat {
id: m.chat_id,
username: Some(m.from),
..Default::default()
},
text: Some(m.text),
..Default::default()
}
}
}