Skip to main content

layer_client/
search.rs

1// Copyright (c) Ankit Chaubey <ankitchaubey.dev@gmail.com>
2// SPDX-License-Identifier: MIT OR Apache-2.0
3
4// NOTE:
5// The "Layer" project is no longer maintained or supported.
6// Its original purpose for personal SDK/APK experimentation and learning
7// has been fulfilled.
8//
9// Please use Ferogram instead:
10// https://github.com/ankit-chaubey/ferogram
11// Ferogram will receive future updates and development, although progress
12// may be slower.
13//
14// Ferogram is an async Telegram MTProto client library written in Rust.
15// Its implementation follows the behaviour of the official Telegram clients,
16// particularly Telegram Desktop and TDLib, and aims to provide a clean and
17// modern async interface for building Telegram clients and tools.
18
19//! Fluent search builders.
20//!
21//! # In-chat search
22//! ```rust,no_run
23//! # async fn f(client: layer_client::Client, peer: layer_tl_types::enums::Peer)
24//! # -> Result<(), Box<dyn std::error::Error>> {
25//! let results = client
26//! .search(peer, "hello world")
27//! .min_date(1_700_000_000)
28//! .max_date(1_720_000_000)
29//! .filter(layer_tl_types::enums::MessagesFilter::InputMessagesFilterPhotos)
30//! .limit(50)
31//! .fetch(&client)
32//! .await?;
33//! # Ok(()) }
34//! ```
35//!
36//! # Global search
37//! ```rust,no_run
38//! # async fn f(client: layer_client::Client)
39//! # -> Result<(), Box<dyn std::error::Error>> {
40//! let results = client
41//! .search_global_builder("rust async")
42//! .broadcasts_only(true)
43//! .min_date(1_700_000_000)
44//! .limit(30)
45//! .fetch(&client)
46//! .await?;
47//! # Ok(()) }
48//! ```
49
50use layer_tl_types::{self as tl, Cursor, Deserializable};
51
52use crate::{Client, InvocationError, PeerRef, update};
53
54/// Fluent builder for `messages.search` (in-chat message search).
55///
56/// Created by [`Client::search`]. All setters are chainable; call
57/// [`fetch`] to execute.
58///
59/// [`fetch`]: SearchBuilder::fetch
60pub struct SearchBuilder {
61    peer: PeerRef,
62    query: String,
63    filter: tl::enums::MessagesFilter,
64    min_date: i32,
65    max_date: i32,
66    offset_id: i32,
67    add_offset: i32,
68    limit: i32,
69    max_id: i32,
70    min_id: i32,
71    from_id: Option<tl::enums::InputPeer>,
72    top_msg_id: Option<i32>,
73}
74
75impl SearchBuilder {
76    pub(crate) fn new(peer: PeerRef, query: String) -> Self {
77        Self {
78            peer,
79            query,
80            filter: tl::enums::MessagesFilter::InputMessagesFilterEmpty,
81            min_date: 0,
82            max_date: 0,
83            offset_id: 0,
84            add_offset: 0,
85            limit: 100,
86            max_id: 0,
87            min_id: 0,
88            from_id: None,
89            top_msg_id: None,
90        }
91    }
92
93    /// Only return messages on or after this Unix timestamp.
94    pub fn min_date(mut self, ts: i32) -> Self {
95        self.min_date = ts;
96        self
97    }
98
99    /// Only return messages on or before this Unix timestamp.
100    pub fn max_date(mut self, ts: i32) -> Self {
101        self.max_date = ts;
102        self
103    }
104
105    /// Apply a `MessagesFilter` (e.g. photos only, video only, etc.).
106    pub fn filter(mut self, f: tl::enums::MessagesFilter) -> Self {
107        self.filter = f;
108        self
109    }
110
111    /// Maximum number of messages to return (default 100).
112    pub fn limit(mut self, n: i32) -> Self {
113        self.limit = n;
114        self
115    }
116
117    /// Start from this message ID (for pagination).
118    pub fn offset_id(mut self, id: i32) -> Self {
119        self.offset_id = id;
120        self
121    }
122
123    /// Additional offset for fine-grained pagination.
124    pub fn add_offset(mut self, off: i32) -> Self {
125        self.add_offset = off;
126        self
127    }
128
129    /// Only return messages with an ID ≤ `max_id`.
130    pub fn max_id(mut self, id: i32) -> Self {
131        self.max_id = id;
132        self
133    }
134
135    /// Only return messages with an ID ≥ `min_id`.
136    pub fn min_id(mut self, id: i32) -> Self {
137        self.min_id = id;
138        self
139    }
140
141    /// Restrict to messages sent by this peer (resolved against the cache).
142    /// Only return messages sent by the logged-in user.
143    pub fn sent_by_self(mut self) -> Self {
144        self.from_id = Some(tl::enums::InputPeer::PeerSelf);
145        self
146    }
147
148    /// Only return messages sent by a specific peer.
149    pub fn from_peer(mut self, peer: tl::enums::InputPeer) -> Self {
150        self.from_id = Some(peer);
151        self
152    }
153
154    /// Restrict search to a specific forum topic.
155    pub fn top_msg_id(mut self, id: i32) -> Self {
156        self.top_msg_id = Some(id);
157        self
158    }
159
160    /// Execute the search and return matching messages.
161    pub async fn fetch(
162        self,
163        client: &Client,
164    ) -> Result<Vec<update::IncomingMessage>, InvocationError> {
165        let peer = self.peer.resolve(client).await?;
166        let input_peer = client.inner.peer_cache.read().await.peer_to_input(&peer);
167        let req = tl::functions::messages::Search {
168            peer: input_peer,
169            q: self.query,
170            from_id: self.from_id,
171            saved_peer_id: None,
172            saved_reaction: None,
173            top_msg_id: self.top_msg_id,
174            filter: self.filter,
175            min_date: self.min_date,
176            max_date: self.max_date,
177            offset_id: self.offset_id,
178            add_offset: self.add_offset,
179            limit: self.limit,
180            max_id: self.max_id,
181            min_id: self.min_id,
182            hash: 0,
183        };
184        let body = client.rpc_call_raw(&req).await?;
185        let mut cur = Cursor::from_slice(&body);
186        let msgs = match tl::enums::messages::Messages::deserialize(&mut cur)? {
187            tl::enums::messages::Messages::Messages(m) => m.messages,
188            tl::enums::messages::Messages::Slice(m) => m.messages,
189            tl::enums::messages::Messages::ChannelMessages(m) => m.messages,
190            tl::enums::messages::Messages::NotModified(_) => vec![],
191        };
192        Ok(msgs
193            .into_iter()
194            .map(update::IncomingMessage::from_raw)
195            .collect())
196    }
197}
198
199/// Fluent builder for `messages.searchGlobal` (cross-chat search).
200///
201/// Created by [`Client::search_global_builder`]. All setters are chainable;
202/// call [`fetch`] to execute.
203///
204/// [`fetch`]: GlobalSearchBuilder::fetch
205pub struct GlobalSearchBuilder {
206    query: String,
207    filter: tl::enums::MessagesFilter,
208    min_date: i32,
209    max_date: i32,
210    offset_rate: i32,
211    offset_id: i32,
212    limit: i32,
213    folder_id: Option<i32>,
214    broadcasts_only: bool,
215    groups_only: bool,
216    users_only: bool,
217}
218
219impl GlobalSearchBuilder {
220    pub(crate) fn new(query: String) -> Self {
221        Self {
222            query,
223            filter: tl::enums::MessagesFilter::InputMessagesFilterEmpty,
224            min_date: 0,
225            max_date: 0,
226            offset_rate: 0,
227            offset_id: 0,
228            limit: 100,
229            folder_id: None,
230            broadcasts_only: false,
231            groups_only: false,
232            users_only: false,
233        }
234    }
235
236    /// Restrict to a specific dialog folder.
237    pub fn folder_id(mut self, id: i32) -> Self {
238        self.folder_id = Some(id);
239        self
240    }
241
242    /// Only return results from broadcast channels.
243    pub fn broadcasts_only(mut self, v: bool) -> Self {
244        self.broadcasts_only = v;
245        self
246    }
247
248    /// Only return results from groups/supergroups.
249    pub fn groups_only(mut self, v: bool) -> Self {
250        self.groups_only = v;
251        self
252    }
253
254    /// Only return results from private chats / bots.
255    pub fn users_only(mut self, v: bool) -> Self {
256        self.users_only = v;
257        self
258    }
259
260    /// Apply a `MessagesFilter` (e.g. photos, video, etc.).
261    pub fn filter(mut self, f: tl::enums::MessagesFilter) -> Self {
262        self.filter = f;
263        self
264    }
265
266    /// Only return messages on or after this Unix timestamp.
267    pub fn min_date(mut self, ts: i32) -> Self {
268        self.min_date = ts;
269        self
270    }
271
272    /// Only return messages on or before this Unix timestamp.
273    pub fn max_date(mut self, ts: i32) -> Self {
274        self.max_date = ts;
275        self
276    }
277
278    /// Pagination: rate from the previous response's last message.
279    pub fn offset_rate(mut self, r: i32) -> Self {
280        self.offset_rate = r;
281        self
282    }
283
284    /// Pagination: start from this message ID.
285    pub fn offset_id(mut self, id: i32) -> Self {
286        self.offset_id = id;
287        self
288    }
289
290    /// Maximum number of messages to return (default 100).
291    pub fn limit(mut self, n: i32) -> Self {
292        self.limit = n;
293        self
294    }
295
296    /// Execute the global search and return matching messages.
297    pub async fn fetch(
298        self,
299        client: &Client,
300    ) -> Result<Vec<update::IncomingMessage>, InvocationError> {
301        let req = tl::functions::messages::SearchGlobal {
302            broadcasts_only: self.broadcasts_only,
303            groups_only: self.groups_only,
304            users_only: self.users_only,
305            folder_id: self.folder_id,
306            q: self.query,
307            filter: self.filter,
308            min_date: self.min_date,
309            max_date: self.max_date,
310            offset_rate: self.offset_rate,
311            offset_peer: tl::enums::InputPeer::Empty,
312            offset_id: self.offset_id,
313            limit: self.limit,
314        };
315        let body = client.rpc_call_raw(&req).await?;
316        let mut cur = Cursor::from_slice(&body);
317        let msgs = match tl::enums::messages::Messages::deserialize(&mut cur)? {
318            tl::enums::messages::Messages::Messages(m) => m.messages,
319            tl::enums::messages::Messages::Slice(m) => m.messages,
320            tl::enums::messages::Messages::ChannelMessages(m) => m.messages,
321            tl::enums::messages::Messages::NotModified(_) => vec![],
322        };
323        Ok(msgs
324            .into_iter()
325            .map(update::IncomingMessage::from_raw)
326            .collect())
327    }
328}