use std::collections::{HashMap, HashSet, VecDeque};
use std::pin::Pin;
use std::task::{Context, Poll};
use ferogram_tl_types as tl;
use futures::stream::{Stream, try_unfold};
use serde::{Deserialize, Serialize};
use tl::Serializable;
use crate::Client;
use crate::errors::InvocationError;
use crate::peer_ref::PeerRef;
use crate::update;
#[derive(Debug, Clone)]
pub struct Dialog {
pub raw: tl::enums::Dialog,
pub message: Option<tl::enums::Message>,
pub entity: Option<tl::enums::User>,
pub chat: Option<tl::enums::Chat>,
}
impl Dialog {
pub fn title(&self) -> String {
if let Some(tl::enums::User::User(u)) = &self.entity {
let first = u.first_name.as_deref().unwrap_or("");
let last = u.last_name.as_deref().unwrap_or("");
let name = format!("{first} {last}").trim().to_string();
if !name.is_empty() {
return name;
}
}
if let Some(chat) = &self.chat {
return match chat {
tl::enums::Chat::Chat(c) => c.title.clone(),
tl::enums::Chat::Forbidden(c) => c.title.clone(),
tl::enums::Chat::Channel(c) => c.title.clone(),
tl::enums::Chat::ChannelForbidden(c) => c.title.clone(),
tl::enums::Chat::Empty(_) => "(empty)".into(),
tl::enums::Chat::Community(c) => c.title.clone(),
tl::enums::Chat::CommunityForbidden(c) => c.title.clone(),
};
}
"(Unknown)".to_string()
}
pub fn peer(&self) -> Option<&tl::enums::Peer> {
match &self.raw {
tl::enums::Dialog::Dialog(d) => Some(&d.peer),
tl::enums::Dialog::Folder(_) => None,
tl::enums::Dialog::Community(_) => None,
}
}
pub fn unread_count(&self) -> i32 {
match &self.raw {
tl::enums::Dialog::Dialog(d) => d.unread_count,
_ => 0,
}
}
pub fn top_message(&self) -> i32 {
match &self.raw {
tl::enums::Dialog::Dialog(d) => d.top_message,
_ => 0,
}
}
pub fn matches_filter(&self, filter: &FlattenedDialogFilter) -> bool {
if filter.is_default {
return true;
}
let Some(peer) = self.peer() else {
return false;
};
let id = peer_id(peer);
if filter.exclude_peers.contains(&id) {
return false;
}
if filter.pinned_peers.contains_key(&id) || filter.include_peers.contains(&id) {
return true;
}
if filter.exclude_archived
&& let tl::enums::Dialog::Dialog(d) = &self.raw
&& d.folder_id == Some(1)
{
return false;
}
if filter.exclude_read && self.unread_count() == 0 {
return false;
}
if filter.exclude_muted
&& let tl::enums::Dialog::Dialog(d) = &self.raw
{
let tl::enums::PeerNotifySettings::PeerNotifySettings(n) = &d.notify_settings;
if let Some(until) = n.mute_until
&& until > chrono::Utc::now().timestamp() as i32
{
return false;
}
}
let is_bot = matches!(&self.entity, Some(tl::enums::User::User(u)) if u.bot);
let is_contact = matches!(&self.entity, Some(tl::enums::User::User(u)) if u.contact);
let is_broadcast = matches!(&self.chat, Some(tl::enums::Chat::Channel(c)) if c.broadcast);
let is_group = self.chat.is_some() && !is_broadcast;
(filter.include_bots && is_bot)
|| (filter.include_contacts && self.entity.is_some() && is_contact && !is_bot)
|| (filter.include_non_contacts && self.entity.is_some() && !is_contact && !is_bot)
|| (filter.include_groups && is_group)
|| (filter.include_broadcasts && is_broadcast)
}
}
fn peer_id(peer: &tl::enums::Peer) -> i64 {
match peer {
tl::enums::Peer::User(p) => p.user_id,
tl::enums::Peer::Chat(p) => p.chat_id,
tl::enums::Peer::Channel(p) => p.channel_id,
}
}
fn input_peer_id(peer: &tl::enums::InputPeer) -> Option<i64> {
match peer {
tl::enums::InputPeer::User(p) => Some(p.user_id),
tl::enums::InputPeer::Chat(p) => Some(p.chat_id),
tl::enums::InputPeer::Channel(p) => Some(p.channel_id),
_ => None,
}
}
fn peer_id_set(list: &[tl::enums::InputPeer]) -> HashSet<i64> {
list.iter().filter_map(input_peer_id).collect()
}
fn peer_id_index(list: &[tl::enums::InputPeer]) -> HashMap<i64, usize> {
list.iter()
.filter_map(input_peer_id)
.enumerate()
.map(|(i, id)| (id, i))
.collect()
}
#[derive(Debug, Clone, Default)]
pub struct FlattenedDialogFilter {
pub is_default: bool,
pub include_contacts: bool,
pub include_non_contacts: bool,
pub include_groups: bool,
pub include_broadcasts: bool,
pub include_bots: bool,
pub exclude_muted: bool,
pub exclude_read: bool,
pub exclude_archived: bool,
pub include_peers: HashSet<i64>,
pub exclude_peers: HashSet<i64>,
pub pinned_peers: HashMap<i64, usize>,
}
impl From<&tl::enums::DialogFilter> for FlattenedDialogFilter {
fn from(filter: &tl::enums::DialogFilter) -> Self {
match filter {
tl::enums::DialogFilter::Default => Self {
is_default: true,
..Self::default()
},
tl::enums::DialogFilter::Chatlist(f) => Self {
include_peers: peer_id_set(&f.include_peers),
pinned_peers: peer_id_index(&f.pinned_peers),
..Self::default()
},
tl::enums::DialogFilter::DialogFilter(f) => Self {
is_default: false,
include_contacts: f.contacts,
include_non_contacts: f.non_contacts,
include_groups: f.groups,
include_broadcasts: f.broadcasts,
include_bots: f.bots,
exclude_muted: f.exclude_muted,
exclude_read: f.exclude_read,
exclude_archived: f.exclude_archived,
include_peers: peer_id_set(&f.include_peers),
exclude_peers: peer_id_set(&f.exclude_peers),
pinned_peers: peer_id_index(&f.pinned_peers),
},
}
}
}
pub(crate) fn dialog_filter_id(filter: &tl::enums::DialogFilter) -> i32 {
match filter {
tl::enums::DialogFilter::Default => 0,
tl::enums::DialogFilter::DialogFilter(f) => f.id,
tl::enums::DialogFilter::Chatlist(f) => f.id,
}
}
pub(crate) fn scan_archive_for(filter: &FlattenedDialogFilter) -> bool {
!filter.is_default && !filter.exclude_archived
}
#[derive(Default, Clone, Copy)]
pub struct GetDialogsOptions {
pub limit: i32,
pub exclude_pinned: bool,
pub folder_id: Option<i32>,
}
impl From<i32> for GetDialogsOptions {
fn from(limit: i32) -> Self {
Self {
limit,
..Default::default()
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DialogCursor {
pub(crate) offset_date: i32,
pub(crate) offset_id: i32,
pub(crate) offset_peer: Vec<u8>,
pub(crate) exclude_pinned: bool,
pub(crate) folder_id: Option<i32>,
pub(crate) total: Option<i32>,
}
impl Default for DialogCursor {
fn default() -> Self {
Self {
offset_date: 0,
offset_id: 0,
offset_peer: tl::enums::InputPeer::Empty.to_bytes(),
exclude_pinned: false,
folder_id: None,
total: None,
}
}
}
impl DialogCursor {
pub fn new(
offset_date: i32,
offset_id: i32,
offset_peer: tl::enums::InputPeer,
exclude_pinned: bool,
folder_id: Option<i32>,
) -> Self {
Self {
offset_date,
offset_id,
offset_peer: offset_peer.to_bytes(),
exclude_pinned,
folder_id,
total: None,
}
}
pub fn exclude_pinned(&self) -> bool {
self.exclude_pinned
}
pub fn folder_id(&self) -> Option<i32> {
self.folder_id
}
pub fn total(&self) -> Option<i32> {
self.total
}
}
pub struct DialogIter {
pub(crate) offset_date: i32,
pub(crate) offset_id: i32,
pub(crate) offset_peer: tl::enums::InputPeer,
pub(crate) exclude_pinned: bool,
pub(crate) folder_id: Option<i32>,
pub(crate) done: bool,
pub(crate) buffer: VecDeque<Dialog>,
pub total: Option<i32>,
}
impl DialogIter {
const PAGE_SIZE: i32 = 100;
pub fn total(&self) -> Option<i32> {
self.total
}
pub fn exclude_pinned(mut self, v: bool) -> Self {
self.exclude_pinned = v;
self
}
pub fn folder_id(mut self, id: Option<i32>) -> Self {
self.folder_id = id;
self
}
pub fn cursor(&self) -> DialogCursor {
DialogCursor {
offset_date: self.offset_date,
offset_id: self.offset_id,
offset_peer: self.offset_peer.to_bytes(),
exclude_pinned: self.exclude_pinned,
folder_id: self.folder_id,
total: self.total,
}
}
pub async fn next(&mut self, client: &Client) -> Result<Option<Dialog>, InvocationError> {
if let Some(d) = self.buffer.pop_front() {
return Ok(Some(d));
}
if self.done {
return Ok(None);
}
let req = tl::functions::messages::GetDialogs {
exclude_pinned: self.exclude_pinned,
folder_id: self.folder_id,
offset_date: self.offset_date,
offset_id: self.offset_id,
offset_peer: self.offset_peer.clone(),
limit: Self::PAGE_SIZE,
hash: 0,
};
let (dialogs, count): (Vec<crate::Dialog>, Option<i32>) =
client.get_dialogs_raw_with_count(req).await?;
if self.total.is_none() {
self.total = count;
}
if dialogs.is_empty() || dialogs.len() < Self::PAGE_SIZE as usize {
self.done = true;
}
if let Some(last) = dialogs.last() {
self.offset_date = last
.message
.as_ref()
.map(|m| match m {
tl::enums::Message::Message(x) => x.date,
tl::enums::Message::Service(x) => x.date,
_ => 0,
})
.unwrap_or(0);
self.offset_id = last.top_message();
if let Some(peer) = last.peer() {
self.offset_peer = client.inner.peer_cache.read().await.peer_to_input(peer)?;
}
}
self.buffer.extend(dialogs);
Ok(self.buffer.pop_front())
}
}
pub struct DialogsStream {
inner: Pin<Box<dyn Stream<Item = Result<Dialog, InvocationError>> + Send>>,
}
impl DialogsStream {
pub(crate) fn new(client: Client, iter: DialogIter) -> Self {
let raw = try_unfold((client, iter), |(client, mut iter)| async move {
match iter.next(&client).await? {
Some(dialog) => Ok(Some((dialog, (client, iter)))),
None => Ok(None),
}
});
Self {
inner: Box::pin(raw),
}
}
pub(crate) fn boxed(
inner: impl Stream<Item = Result<Dialog, InvocationError>> + Send + 'static,
) -> Self {
Self {
inner: Box::pin(inner),
}
}
}
pub struct DialogFilterIter {
pub(crate) filter_id: i32,
pub(crate) filter: FlattenedDialogFilter,
pub(crate) main: DialogIter,
pub(crate) archived: DialogIter,
pub(crate) scan_archive: bool,
pub(crate) in_archive: bool,
}
impl DialogFilterIter {
pub async fn next(&mut self, client: &Client) -> Result<Option<Dialog>, InvocationError> {
loop {
let next = if !self.in_archive {
match self.main.next(client).await? {
Some(d) => Some(d),
None => {
if !self.scan_archive {
return Ok(None);
}
self.in_archive = true;
continue;
}
}
} else {
self.archived.next(client).await?
};
match next {
Some(d) if d.matches_filter(&self.filter) => return Ok(Some(d)),
Some(_) => continue,
None => return Ok(None),
}
}
}
pub fn cursor(&self) -> DialogFilterCursor {
DialogFilterCursor {
filter_id: self.filter_id,
main: self.main.cursor(),
archived: self.archived.cursor(),
in_archive: self.in_archive,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DialogFilterCursor {
pub(crate) filter_id: i32,
pub(crate) main: DialogCursor,
pub(crate) archived: DialogCursor,
pub(crate) in_archive: bool,
}
impl Stream for DialogsStream {
type Item = Result<Dialog, InvocationError>;
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
self.inner.as_mut().poll_next(cx)
}
}
pub struct MessageIter {
pub(crate) unresolved: Option<PeerRef>,
pub(crate) peer: Option<tl::enums::Peer>,
pub(crate) offset_id: i32,
pub(crate) done: bool,
pub(crate) buffer: VecDeque<update::IncomingMessage>,
pub total: Option<i32>,
}
impl MessageIter {
const PAGE_SIZE: i32 = 100;
pub fn total(&self) -> Option<i32> {
self.total
}
pub async fn next(
&mut self,
client: &Client,
) -> Result<Option<update::IncomingMessage>, InvocationError> {
if let Some(m) = self.buffer.pop_front() {
return Ok(Some(m));
}
if self.done {
return Ok(None);
}
let peer = if let Some(p) = &self.peer {
p.clone()
} else {
let pr = self.unresolved.take().expect("MessageIter: peer not set");
let p = pr.resolve(client).await?;
self.peer = Some(p.clone());
p
};
let input_peer = client.inner.peer_cache.read().await.peer_to_input(&peer)?;
let (page, count): (Vec<crate::update::IncomingMessage>, Option<i32>) = client
.get_messages_with_count(input_peer, Self::PAGE_SIZE, self.offset_id)
.await?;
if self.total.is_none() {
self.total = count;
}
if page.is_empty() || page.len() < Self::PAGE_SIZE as usize {
self.done = true;
}
if let Some(last) = page.last() {
self.offset_id = last.id();
}
self.buffer.extend(page);
Ok(self.buffer.pop_front())
}
}