Skip to main content

layer_client/
search.rs

1//! G-31 / G-32 — Fluent search builders.
2//!
3//! # In-chat search (G-31)
4//! ```rust,no_run
5//! # async fn f(client: layer_client::Client, peer: layer_tl_types::enums::Peer)
6//! # -> Result<(), Box<dyn std::error::Error>> {
7//! let results = client
8//!     .search(peer, "hello world")
9//!     .min_date(1_700_000_000)
10//!     .max_date(1_720_000_000)
11//!     .filter(layer_tl_types::enums::MessagesFilter::InputMessagesFilterPhotos)
12//!     .limit(50)
13//!     .fetch(&client)
14//!     .await?;
15//! # Ok(()) }
16//! ```
17//!
18//! # Global search (G-32)
19//! ```rust,no_run
20//! # async fn f(client: layer_client::Client)
21//! # -> Result<(), Box<dyn std::error::Error>> {
22//! let results = client
23//!     .search_global_builder("rust async")
24//!     .broadcasts_only(true)
25//!     .min_date(1_700_000_000)
26//!     .limit(30)
27//!     .fetch(&client)
28//!     .await?;
29//! # Ok(()) }
30//! ```
31
32use layer_tl_types::{self as tl, Cursor, Deserializable};
33
34use crate::{Client, InvocationError, update};
35
36// ─── SearchBuilder (G-31) ─────────────────────────────────────────────────────
37
38/// Fluent builder for `messages.search` (in-chat message search).
39///
40/// Created by [`Client::search`]. All setters are chainable; call
41/// [`fetch`] to execute.
42///
43/// [`fetch`]: SearchBuilder::fetch
44pub struct SearchBuilder {
45    peer:       tl::enums::Peer,
46    query:      String,
47    filter:     tl::enums::MessagesFilter,
48    min_date:   i32,
49    max_date:   i32,
50    offset_id:  i32,
51    add_offset: i32,
52    limit:      i32,
53    max_id:     i32,
54    min_id:     i32,
55    from_id:    Option<tl::enums::InputPeer>,
56    top_msg_id: Option<i32>,
57}
58
59impl SearchBuilder {
60    pub(crate) fn new(peer: tl::enums::Peer, query: String) -> Self {
61        Self {
62            peer,
63            query,
64            filter:     tl::enums::MessagesFilter::InputMessagesFilterEmpty,
65            min_date:   0,
66            max_date:   0,
67            offset_id:  0,
68            add_offset: 0,
69            limit:      100,
70            max_id:     0,
71            min_id:     0,
72            from_id:    None,
73            top_msg_id: None,
74        }
75    }
76
77    /// Only return messages on or after this Unix timestamp.
78    pub fn min_date(mut self, ts: i32) -> Self { self.min_date = ts; self }
79
80    /// Only return messages on or before this Unix timestamp.
81    pub fn max_date(mut self, ts: i32) -> Self { self.max_date = ts; self }
82
83    /// Apply a `MessagesFilter` (e.g. photos only, video only, etc.).
84    pub fn filter(mut self, f: tl::enums::MessagesFilter) -> Self { self.filter = f; self }
85
86    /// Maximum number of messages to return (default 100).
87    pub fn limit(mut self, n: i32) -> Self { self.limit = n; self }
88
89    /// Start from this message ID (for pagination).
90    pub fn offset_id(mut self, id: i32) -> Self { self.offset_id = id; self }
91
92    /// Additional offset for fine-grained pagination.
93    pub fn add_offset(mut self, off: i32) -> Self { self.add_offset = off; self }
94
95    /// Only return messages with an ID ≤ `max_id`.
96    pub fn max_id(mut self, id: i32) -> Self { self.max_id = id; self }
97
98    /// Only return messages with an ID ≥ `min_id`.
99    pub fn min_id(mut self, id: i32) -> Self { self.min_id = id; self }
100
101    /// Restrict to messages sent by this peer (resolved against the cache).
102    pub fn from_peer(mut self, peer: tl::enums::InputPeer) -> Self {
103        self.from_id = Some(peer);
104        self
105    }
106
107    /// Restrict search to a specific forum topic.
108    pub fn top_msg_id(mut self, id: i32) -> Self { self.top_msg_id = Some(id); self }
109
110    /// Execute the search and return matching messages.
111    pub async fn fetch(
112        self,
113        client: &Client,
114    ) -> Result<Vec<update::IncomingMessage>, InvocationError> {
115        let input_peer = client.inner.peer_cache.read().await.peer_to_input(&self.peer);
116        let req = tl::functions::messages::Search {
117            peer:           input_peer,
118            q:              self.query,
119            from_id:        self.from_id,
120            saved_peer_id:  None,
121            saved_reaction: None,
122            top_msg_id:     self.top_msg_id,
123            filter:         self.filter,
124            min_date:       self.min_date,
125            max_date:       self.max_date,
126            offset_id:      self.offset_id,
127            add_offset:     self.add_offset,
128            limit:          self.limit,
129            max_id:         self.max_id,
130            min_id:         self.min_id,
131            hash:           0,
132        };
133        let body    = client.rpc_call_raw(&req).await?;
134        let mut cur = Cursor::from_slice(&body);
135        let msgs = match tl::enums::messages::Messages::deserialize(&mut cur)? {
136            tl::enums::messages::Messages::Messages(m)        => m.messages,
137            tl::enums::messages::Messages::Slice(m)           => m.messages,
138            tl::enums::messages::Messages::ChannelMessages(m) => m.messages,
139            tl::enums::messages::Messages::NotModified(_)     => vec![],
140        };
141        Ok(msgs.into_iter().map(update::IncomingMessage::from_raw).collect())
142    }
143}
144
145// ─── GlobalSearchBuilder (G-32) ───────────────────────────────────────────────
146
147/// Fluent builder for `messages.searchGlobal` (cross-chat search).
148///
149/// Created by [`Client::search_global_builder`]. All setters are chainable;
150/// call [`fetch`] to execute.
151///
152/// [`fetch`]: GlobalSearchBuilder::fetch
153pub struct GlobalSearchBuilder {
154    query:           String,
155    filter:          tl::enums::MessagesFilter,
156    min_date:        i32,
157    max_date:        i32,
158    offset_rate:     i32,
159    offset_id:       i32,
160    limit:           i32,
161    folder_id:       Option<i32>,
162    broadcasts_only: bool,
163    groups_only:     bool,
164    users_only:      bool,
165}
166
167impl GlobalSearchBuilder {
168    pub(crate) fn new(query: String) -> Self {
169        Self {
170            query,
171            filter:          tl::enums::MessagesFilter::InputMessagesFilterEmpty,
172            min_date:        0,
173            max_date:        0,
174            offset_rate:     0,
175            offset_id:       0,
176            limit:           100,
177            folder_id:       None,
178            broadcasts_only: false,
179            groups_only:     false,
180            users_only:      false,
181        }
182    }
183
184    /// Restrict to a specific dialog folder.
185    pub fn folder_id(mut self, id: i32) -> Self { self.folder_id = Some(id); self }
186
187    /// Only return results from broadcast channels.
188    pub fn broadcasts_only(mut self, v: bool) -> Self { self.broadcasts_only = v; self }
189
190    /// Only return results from groups/supergroups.
191    pub fn groups_only(mut self, v: bool) -> Self { self.groups_only = v; self }
192
193    /// Only return results from private chats / bots.
194    pub fn users_only(mut self, v: bool) -> Self { self.users_only = v; self }
195
196    /// Apply a `MessagesFilter` (e.g. photos, video, etc.).
197    pub fn filter(mut self, f: tl::enums::MessagesFilter) -> Self { self.filter = f; self }
198
199    /// Only return messages on or after this Unix timestamp.
200    pub fn min_date(mut self, ts: i32) -> Self { self.min_date = ts; self }
201
202    /// Only return messages on or before this Unix timestamp.
203    pub fn max_date(mut self, ts: i32) -> Self { self.max_date = ts; self }
204
205    /// Pagination: rate from the previous response's last message.
206    pub fn offset_rate(mut self, r: i32) -> Self { self.offset_rate = r; self }
207
208    /// Pagination: start from this message ID.
209    pub fn offset_id(mut self, id: i32) -> Self { self.offset_id = id; self }
210
211    /// Maximum number of messages to return (default 100).
212    pub fn limit(mut self, n: i32) -> Self { self.limit = n; self }
213
214    /// Execute the global search and return matching messages.
215    pub async fn fetch(
216        self,
217        client: &Client,
218    ) -> Result<Vec<update::IncomingMessage>, InvocationError> {
219        let req = tl::functions::messages::SearchGlobal {
220            broadcasts_only: self.broadcasts_only,
221            groups_only:     self.groups_only,
222            users_only:      self.users_only,
223            folder_id:       self.folder_id,
224            q:               self.query,
225            filter:          self.filter,
226            min_date:        self.min_date,
227            max_date:        self.max_date,
228            offset_rate:     self.offset_rate,
229            offset_peer:     tl::enums::InputPeer::Empty,
230            offset_id:       self.offset_id,
231            limit:           self.limit,
232        };
233        let body    = client.rpc_call_raw(&req).await?;
234        let mut cur = Cursor::from_slice(&body);
235        let msgs = match tl::enums::messages::Messages::deserialize(&mut cur)? {
236            tl::enums::messages::Messages::Messages(m)        => m.messages,
237            tl::enums::messages::Messages::Slice(m)           => m.messages,
238            tl::enums::messages::Messages::ChannelMessages(m) => m.messages,
239            tl::enums::messages::Messages::NotModified(_)     => vec![],
240        };
241        Ok(msgs.into_iter().map(update::IncomingMessage::from_raw).collect())
242    }
243}