use std::time::Duration;
use tokio::time::timeout;
use crate::update::{self, CallbackQuery, IncomingMessage};
use crate::{Client, InvocationError, PeerRef, UpdateStream};
use ferogram_tl_types as tl;
#[derive(Debug)]
pub enum ConversationError {
Timeout(Duration),
StreamClosed,
Invocation(InvocationError),
}
impl std::fmt::Display for ConversationError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Timeout(d) => write!(f, "conversation timed out after {d:?}"),
Self::StreamClosed => write!(f, "update stream closed"),
Self::Invocation(e) => write!(f, "{e}"),
}
}
}
impl std::error::Error for ConversationError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
if let Self::Invocation(e) = self {
Some(e)
} else {
None
}
}
}
impl From<InvocationError> for ConversationError {
fn from(e: InvocationError) -> Self {
Self::Invocation(e)
}
}
pub struct Conversation<'a> {
client: Client,
peer: tl::enums::Peer,
stream: &'a mut UpdateStream,
buffered: Vec<update::Update>,
}
impl<'a> Conversation<'a> {
pub async fn new(
client: &Client,
stream: &'a mut UpdateStream,
peer: impl Into<PeerRef>,
) -> Result<Self, ConversationError> {
let peer = peer.into().resolve(client).await?;
Ok(Self {
client: client.clone(),
peer,
stream,
buffered: Vec::new(),
})
}
pub async fn ask(&self, text: impl Into<String>) -> Result<IncomingMessage, ConversationError> {
let s: String = text.into();
Ok(self
.client
.send_message(self.peer.clone(), s.as_str())
.await?)
}
pub async fn get_response(
&mut self,
deadline: Duration,
) -> Result<IncomingMessage, ConversationError> {
let start = tokio::time::Instant::now();
loop {
let remaining = deadline.checked_sub(start.elapsed()).unwrap_or_default();
if remaining.is_zero() {
return Err(ConversationError::Timeout(deadline));
}
match timeout(remaining, self.stream.next()).await {
Err(_) => return Err(ConversationError::Timeout(deadline)),
Ok(None) => return Err(ConversationError::StreamClosed),
Ok(Some(upd)) => match upd {
update::Update::NewMessage(ref msg)
if peer_matches(msg.peer_id(), &self.peer) =>
{
return Ok(msg.clone());
}
other => self.buffered.push(other),
},
}
}
}
pub async fn wait_click(
&mut self,
deadline: Duration,
) -> Result<CallbackQuery, ConversationError> {
let start = tokio::time::Instant::now();
loop {
let remaining = deadline.checked_sub(start.elapsed()).unwrap_or_default();
if remaining.is_zero() {
return Err(ConversationError::Timeout(deadline));
}
match timeout(remaining, self.stream.next()).await {
Err(_) => return Err(ConversationError::Timeout(deadline)),
Ok(None) => return Err(ConversationError::StreamClosed),
Ok(Some(upd)) => match upd {
update::Update::CallbackQuery(ref cb) if cb_peer_matches(cb, &self.peer) => {
return Ok(cb.clone());
}
other => self.buffered.push(other),
},
}
}
}
pub async fn wait_read(&mut self, deadline: Duration) -> Result<(), ConversationError> {
let start = tokio::time::Instant::now();
loop {
let remaining = deadline.checked_sub(start.elapsed()).unwrap_or_default();
if remaining.is_zero() {
return Err(ConversationError::Timeout(deadline));
}
match timeout(remaining, self.stream.next()).await {
Err(_) => return Err(ConversationError::Timeout(deadline)),
Ok(None) => return Err(ConversationError::StreamClosed),
Ok(Some(upd)) => {
if let update::Update::Raw(_) = &upd {
return Ok(());
}
self.buffered.push(upd);
}
}
}
}
pub async fn ask_and_wait(
&mut self,
text: impl Into<String>,
deadline: Duration,
) -> Result<IncomingMessage, ConversationError> {
self.ask(text).await?;
self.get_response(deadline).await
}
pub fn peer(&self) -> &tl::enums::Peer {
&self.peer
}
pub fn drain_buffered(&mut self) -> Vec<update::Update> {
std::mem::take(&mut self.buffered)
}
}
fn peer_matches(msg_peer: Option<&tl::enums::Peer>, conv_peer: &tl::enums::Peer) -> bool {
match (msg_peer, conv_peer) {
(Some(tl::enums::Peer::User(a)), tl::enums::Peer::User(b)) => a.user_id == b.user_id,
(Some(tl::enums::Peer::Chat(a)), tl::enums::Peer::Chat(b)) => a.chat_id == b.chat_id,
(Some(tl::enums::Peer::Channel(a)), tl::enums::Peer::Channel(b)) => {
a.channel_id == b.channel_id
}
_ => false,
}
}
fn cb_peer_matches(cb: &CallbackQuery, conv_peer: &tl::enums::Peer) -> bool {
match (&cb.chat_peer, conv_peer) {
(Some(tl::enums::Peer::User(a)), tl::enums::Peer::User(b)) => a.user_id == b.user_id,
(Some(tl::enums::Peer::Chat(a)), tl::enums::Peer::Chat(b)) => a.chat_id == b.chat_id,
(Some(tl::enums::Peer::Channel(a)), tl::enums::Peer::Channel(b)) => {
a.channel_id == b.channel_id
}
_ => {
if let tl::enums::Peer::User(u) = conv_peer {
cb.user_id == u.user_id
} else {
false
}
}
}
}